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
41 changes: 26 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The bucket is the source of truth. The service polls it and mirrors it to `./vau

## Quick start

1. Make a folder with the compose file and the env file:
1. Download the compose file and the env file:

```sh
mkdir hyperhack && cd hyperhack
Expand All @@ -47,13 +47,9 @@ The bucket is the source of truth. The service polls it and mirrors it to `./vau
chmod 600 .env
```

2. Edit `.env` (see [Configuration](#configuration)). Generate each secret with:
`docker-compose.yml` only pulls the released image from GHCR and pins the latest release, for example `ghcr.io/mimic890/hyperhack:0.1.0`. Image tags are the release version without the `v`, and they are never overwritten. There is no `latest` tag.

```sh
openssl rand -base64 48
```

3. Create the data folders. The container runs as user `10001`:
2. Create the data folders. The container runs as user `10001`:

```sh
mkdir data vault
Expand All @@ -62,9 +58,13 @@ The bucket is the source of truth. The service polls it and mirrors it to `./vau

Caddy must be able to read `vault/assets/`. The service writes folders as `0755` and files as `0644`. The SQLite file is `0600`.

4. In `docker-compose.yml`, set `image:` to a released version and remove the `build: .` line. Tags are semver and are never overwritten. There is no `latest` tag.
3. Fill `.env` (see [Configuration](#configuration)). Generate each secret with:

5. Verify the image and start the service:
```sh
openssl rand -base64 48
```

4. Verify the image and start the service. Use the tag from `image:` in `docker-compose.yml`:

```sh
gh attestation verify oci://ghcr.io/mimic890/hyperhack:<version> -R Mimic890/hyperhack
Expand All @@ -75,7 +75,7 @@ The bucket is the source of truth. The service polls it and mirrors it to `./vau

On the first start the service needs the bucket. If S3 is unreachable and there is no mirror yet, it exits with code 1. Later starts build the index from the mirror and do not wait for S3.

6. Set up Caddy (see [Caddy](#caddy)) and set `TRUSTED_PROXIES`.
5. Set up Caddy (see [Caddy](#caddy)) and set `TRUSTED_PROXIES`.

The service listens on port `8080` inside the container. Compose publishes it on `127.0.0.1:8080` only. Do not expose it to the internet: Caddy is the only public entry.

Expand Down Expand Up @@ -384,11 +384,20 @@ Vote cookies (`vid`) are random and do not depend on any secret.

## Upgrading

Set the new version in `docker-compose.yml`, verify it with `gh attestation verify`, then:
Versions are listed under [tags](https://github.com/Mimic890/hyperhack/tags). Release `v0.2.0` is image `ghcr.io/mimic890/hyperhack:0.2.0`.

```sh
docker compose pull && docker compose up -d
```
1. Back up `data/` (see [Backups](#backups)).
2. In `docker-compose.yml`, set the new tag in `image:`. If you have not changed the file, you can download it again from `main` instead: it pins the latest release.
3. Verify the new image, pull it and restart:

```sh
gh attestation verify oci://ghcr.io/mimic890/hyperhack:<version> -R Mimic890/hyperhack
docker compose pull
docker compose up -d
docker compose ps # wait for "healthy"
```

To roll back, set the old tag and run `docker compose up -d`. If the old version does not start, restore `data/` from the backup.

## Development

Expand All @@ -397,9 +406,11 @@ Go 1.27 (`toolchain go1.27.3`).
```sh
set -a; . ./.env; set +a
DATA_DIR=./data VAULT_DIR=./vault LOG_FORMAT=text go run .
# or: docker compose up --build
# or, in Docker: docker compose -f compose.dev.yml up --build
```

`compose.dev.yml` builds the image from the checkout as `hyperhack:dev` and otherwise uses the settings of `docker-compose.yml`. Compose never loads it on its own, so production stays on the released image.

Checks, run before every commit and in CI:

```sh
Expand Down
41 changes: 26 additions & 15 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ VictoriaMetrics ──scrape /metrics──▶ hyperhack ; Grafana ──▶ Vi

## Быстрый старт

1. Создайте папку с compose-файлом и файлом окружения:
1. Скачайте compose-файл и файл окружения:

```sh
mkdir hyperhack && cd hyperhack
Expand All @@ -47,13 +47,9 @@ VictoriaMetrics ──scrape /metrics──▶ hyperhack ; Grafana ──▶ Vi
chmod 600 .env
```

2. Заполните `.env` (см. [Настройки](#настройки)). Каждый секрет генерируйте так:
`docker-compose.yml` только скачивает выпущенный образ из GHCR и закрепляет последний релиз, например `ghcr.io/mimic890/hyperhack:0.1.0`. Тег образа — версия релиза без `v`, теги никогда не перезаписываются. Тега `latest` нет.

```sh
openssl rand -base64 48
```

3. Создайте папки для данных. Контейнер работает от пользователя `10001`:
2. Создайте папки для данных. Контейнер работает от пользователя `10001`:

```sh
mkdir data vault
Expand All @@ -62,9 +58,13 @@ VictoriaMetrics ──scrape /metrics──▶ hyperhack ; Grafana ──▶ Vi

Caddy должен иметь право читать `vault/assets/`. Сервис создаёт папки с правами `0755`, файлы — `0644`. Файл SQLite — `0600`.

4. В `docker-compose.yml` укажите в `image:` выпущенную версию и удалите строку `build: .`. Теги — semver, они никогда не перезаписываются. Тега `latest` нет.
3. Заполните `.env` (см. [Настройки](#настройки)). Каждый секрет генерируйте так:

5. Проверьте образ и запустите сервис:
```sh
openssl rand -base64 48
```

4. Проверьте образ и запустите сервис. Тег возьмите из `image:` в `docker-compose.yml`:

```sh
gh attestation verify oci://ghcr.io/mimic890/hyperhack:<version> -R Mimic890/hyperhack
Expand All @@ -75,7 +75,7 @@ VictoriaMetrics ──scrape /metrics──▶ hyperhack ; Grafana ──▶ Vi

При первом запуске сервису нужен бакет. Если S3 недоступен, а зеркала ещё нет, сервис завершается с кодом 1. При следующих запусках индекс строится из зеркала, и сервис не ждёт S3.

6. Настройте Caddy (см. [Caddy](#caddy)) и задайте `TRUSTED_PROXIES`.
5. Настройте Caddy (см. [Caddy](#caddy)) и задайте `TRUSTED_PROXIES`.

Внутри контейнера сервис слушает порт `8080`. Compose публикует его только на `127.0.0.1:8080`. Не открывайте его в интернет: единственный публичный вход — Caddy.

Expand Down Expand Up @@ -384,11 +384,20 @@ Cookie голосов (`vid`) случайные и не зависят ни о

## Обновление

Укажите новую версию в `docker-compose.yml`, проверьте её через `gh attestation verify`, затем:
Версии перечислены в [тегах](https://github.com/Mimic890/hyperhack/tags). Релизу `v0.2.0` соответствует образ `ghcr.io/mimic890/hyperhack:0.2.0`.

```sh
docker compose pull && docker compose up -d
```
1. Сделайте резервную копию `data/` (см. [Резервные копии](#резервные-копии)).
2. В `docker-compose.yml` укажите новый тег в `image:`. Если вы не меняли файл, можно вместо этого скачать его заново из `main`: там закреплён последний релиз.
3. Проверьте новый образ, скачайте его и перезапустите сервис:

```sh
gh attestation verify oci://ghcr.io/mimic890/hyperhack:<version> -R Mimic890/hyperhack
docker compose pull
docker compose up -d
docker compose ps # дождитесь "healthy"
```

Чтобы откатиться, верните старый тег и выполните `docker compose up -d`. Если старая версия не запускается, восстановите `data/` из резервной копии.

## Разработка

Expand All @@ -397,9 +406,11 @@ Go 1.27 (`toolchain go1.27.3`).
```sh
set -a; . ./.env; set +a
DATA_DIR=./data VAULT_DIR=./vault LOG_FORMAT=text go run .
# или: docker compose up --build
# или в Docker: docker compose -f compose.dev.yml up --build
```

`compose.dev.yml` собирает образ из рабочей копии как `hyperhack:dev`, остальные настройки берёт из `docker-compose.yml`. Compose сам его не подключает, поэтому в продакшене остаётся выпущенный образ.

Проверки, перед каждым коммитом и в CI:

```sh
Expand Down
8 changes: 4 additions & 4 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ Proposed variables (final names set during implementation):
## Deployment

- The service runs in Docker and starts with Docker Compose.
- GitHub Actions builds the image. Proposed: push to GHCR (`ghcr.io`).
- GitHub Actions builds the image and pushes it to GHCR (`ghcr.io`).
- `docker-compose.yml` is for production: it only pulls the released image from GHCR, with no `build:`, and pins the latest release. Local builds use `compose.dev.yml` (extends the production service, adds `build: .`, image `hyperhack:dev`), run only with `docker compose -f compose.dev.yml`. No `docker-compose.override.yml`: Compose loads it automatically, so it would reach production.
- Compose uses bind mounts to subfolders (`./data`, …). No named volumes.
- The service port is not published to the internet: localhost, Docker network or mesh IP only. The reverse proxy (Caddy) handles TLS.
- The agent writes `.gitignore` (at least: `.env`, `data/`, build output).
Expand Down Expand Up @@ -556,7 +557,7 @@ Other methods → `405`. Unknown paths → `404`.
- Dependabot: `gomod`, `github-actions`, `docker`.
- CI checks: `go vet`, `staticcheck`, `govulncheck`, tests with `-race`, CodeQL (Go).
- Release: build image, push to GHCR, attest with `actions/attest` (`push-to-registry: true`). Verify on the host: `gh attestation verify oci://ghcr.io/<owner>/hyperhack:<version> -R <owner>/hyperhack`.
- Image tags: semver, never overwritten. Compose pins a version, not `latest`.
- Image tags: semver without the `v` (git tag `v0.1.0` → image `0.1.0`), never overwritten. Compose pins a version, not `latest`. After a release, a PR moves the tag in `docker-compose.yml` to the new version.
- Repo settings: branch protection on `main` (PR and checks required, no force push), secret scanning + push protection, private vulnerability reporting. `SECURITY.md` explains how to report.

### Security tests
Expand All @@ -568,8 +569,6 @@ Other methods → `405`. Unknown paths → `404`.

## Proposed (not confirmed)

- Image registry: GHCR.

## Open questions


Expand Down Expand Up @@ -617,3 +616,4 @@ Other methods → `405`. Unknown paths → `404`.
- 2026-11-04 — Search, tags, RSS, SEO: Bleve `v2.6.1`, in-memory index, one document mapping per language (`en`, `ru`). Fuzziness by word length: ≤ 3 letters 0, ≤ 6 letters 1, longer 2; prefix on the last word. Snippets keep only `<mark>`. Search works without JS; `search.js` swaps the results while typing (debounced). Results page `noindex`. Tags lower case, Obsidian characters, `/tags/<tag>` paginated like the main page. 24 tag hue buckets (FNV-1a hash), contrast checked in tests. RSS: `dc:creator` from `AUTHOR_NAME`, `atom:updated` from `updated`. Image `width`/`height` read from the mirror (`golang.org/x/image` for WebP); an asset change re-renders all posts. IndexNow: one ping per sync with new, changed and removed post URLs, not on the first index load. New `AUTHOR_NAME`; `DEFAULT_OG_IMAGE` is a path inside `assets/`.
- 2026-11-04 — Metrics, votes, limits: SQLite `DATA_DIR/hyperhack.db` (`modernc.org/sqlite` `v1.61.0`, `0600`, WAL). All-time counters per post and label (Grafana gets daily trends with `increase()`), daily views only for "Popular" (pruned after `POPULAR_DAYS`). Search queries: live requests from `search.js` (`X-Search: live`) are not stored, the settled query is; TTL 90 days, at most 10 000 rows, top 20 in `/metrics`, refreshed every 5 min. ALTCHA: `altcha-lib-go/v2` `v2.4.1`, v2 challenge with SHA-256, cost 1, `uint32` counter up to `ALTCHA_MAX_NUMBER`, key signature; own small solver `static/altcha.js` instead of the widget; signing keys derived from `ALTCHA_HMAC_KEY`; replay map capped at 100 000. Beacon: a delta on every hide and `pagehide`, sent with `fetch(…, {keepalive: true})` (JSON body), each with a random per-page-view id; the server sums time per view and clamps the sum to 4 h; time on page = average over views with reading time. Views and read depth: once per visitor per post per day; daily hashes in memory only (cap 200 000), `ip_prefix` = IPv4 address or IPv6 `/64`. Source `internal` for referrers from the site itself. Outbound hosts limited to links in the post. `/api/altcha` and `/api/vote` share `RATE_LIMIT_VOTE`; limiter tracks at most 10 000 clients. Vote cookie stored as SHA-256. "Popular" after "Newest" on page 1. `/metrics` in Prometheus text format written with the standard library.
- 2026-11-04 — Grafana, Caddy, README: dashboard `grafana/hyperhack.json` for a Prometheus-type data source on VictoriaMetrics (uses the VictoriaMetrics function `sort_by_label_numeric`); daily bars cover the day that starts at the bar, so today is shown. `Caddyfile.example` for Caddy `v2.11.7` with `caddy-ratelimit` `v0.1.0` (1200 requests per minute per client IP; `/static/*` and `/assets/*` are not counted) and `caddy-crowdsec-bouncer` `v0.14.1`; `/metrics` and `/reindex` (and their subpaths) answer `404` on the public domain; compression `zstd`/`gzip`. README: behind Caddy on the host, `TRUSTED_PROXIES` is the gateway of the Compose network. Full README in English and Russian.
- 2026-11-04 — Production Compose: image registry GHCR (confirmed). `docker-compose.yml` only pulls the released image, no `build:`, pinned to the latest release (`0.1.0`; image tags are the git tag without `v`). Local builds in `compose.dev.yml`, used only with `-f`; no `docker-compose.override.yml`. README Quick start uses the downloaded `docker-compose.yml` as is, and README documents upgrades and rollback.
Loading
Loading