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
11 changes: 11 additions & 0 deletions .github/workflows/build-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ jobs:
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: v3.16.3

- name: Install dependencies
run: pnpm install --frozen-lockfile

Expand All @@ -36,3 +41,9 @@ jobs:

- name: Test
run: pnpm test

- name: Helm lint
run: make helm-lint

- name: Helm template
run: make helm-template
70 changes: 70 additions & 0 deletions .github/workflows/chart-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Chart Release

on:
push:
branches: [ master ]
paths:
- .github/workflows/chart-release.yaml
- artifacthub-repo.yml
- charts/**
workflow_dispatch:

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Configure Git
run: |
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"

- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.7.0
with:
charts_dir: charts
pages_branch: gh-pages
packages_with_index: true
skip_existing: true
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

- name: Publish Artifact Hub repository metadata
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail

if ! git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1; then
echo "gh-pages branch does not exist yet; skipping artifacthub-repo.yml publication"
exit 0
fi

tmp_dir="$(mktemp -d)"
trap 'rm -rf "${tmp_dir}"' EXIT

git clone --branch gh-pages --single-branch \
"https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
"${tmp_dir}"

install -m 0644 artifacthub-repo.yml "${tmp_dir}/artifacthub-repo.yml"

cd "${tmp_dir}"
if git diff --quiet -- artifacthub-repo.yml; then
echo "artifacthub-repo.yml already up to date on gh-pages"
exit 0
fi

git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git add artifacthub-repo.yml
git commit -m "Publish Artifact Hub repository metadata"
git push origin gh-pages
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@
- Start: `pnpm start`
- Lint: `pnpm lint`
- Test: `pnpm test`
- Helm lint: `make helm-lint` or `helm lint charts/readability-js-server`
- Helm template: `make helm-template` or `helm template readability-js-server charts/readability-js-server`
- Memory soak: `make soak` or `node scripts/memory-soak.js --requests 100 --concurrency 2 --sample-every 10`
- Docker build: `docker build -t readability-js .`
- Docker run: `docker run --rm -p 3000:3000 readability-js`
- Release tag: `make release-tag VERSION=1.8.0`

The Makefile mirrors those workflows with `make install`, `make start`, `make lint`, `make lint-fix`, `make build-container`, `make run-container`, `make release-tag`, and `make example-request`.
The Makefile mirrors those workflows with `make install`, `make start`, `make lint`, `make lint-fix`, `make helm-lint`, `make helm-template`, `make helm-verify`, `make build-container`, `make run-container`, `make release-tag`, and `make example-request`.

`package.json` is the single source of truth for the service version. Release publishing is tag-driven: bump `package.json`'s `version`, commit it, create a matching `vX.Y.Z` tag, and push the tag to trigger Docker publish plus GitHub Release creation.

## Testing expectations

- Run `pnpm lint` and `pnpm test` for any docs, API, config, or dependency change.
- Run `pnpm lint`, `pnpm test`, and the Helm verification targets for any docs, API, config, chart, or dependency change.
- Add or update tests when a change touches response shape, error normalization, URL validation, sanitization, redirect handling, concurrency gating, or config parsing.
- Use the memory soak script when a change could affect allocation behavior or long-run stability.

Expand Down
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ lint:
lint-fix:
pnpm lint:fix

HELM_CHART ?= charts/readability-js-server
HELM_RELEASE_NAME ?= readability-js-server

helm-lint:
helm lint $(HELM_CHART)

helm-template:
helm template $(HELM_RELEASE_NAME) $(HELM_CHART)

helm-verify: helm-lint helm-template

build-container:
docker build -t readability-js .

Expand Down
44 changes: 40 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ At the time of this uplift, `@mozilla/readability@0.6.0` was already the latest
- Sanitization: DOMPurify 3
- Deployment image: `node:24-alpine`

The container runs as a non-root user and the service exposes a single `POST /` endpoint.
The container runs as a non-root user and the service exposes `POST /` plus a lightweight `GET /healthz` probe endpoint. The Compose example uses that same `/healthz` path for its container healthcheck.

## API

Expand Down Expand Up @@ -131,6 +131,34 @@ make start

Release versions come from [`package.json`](package.json). To publish a release, bump `version`, commit the change, create a `vX.Y.Z` tag, and push that tag. The release workflow publishes Docker images for `X.Y.Z`, `X.Y`, `X`, and `latest`, and creates the matching GitHub Release with generated notes.

## Helm chart

The Kubernetes chart is published as a conventional Helm repository on GitHub Pages:

```bash
helm repo add phpdocker-io https://phpdocker-io.github.io/readability-js-server
helm repo update
helm install readability-js-server phpdocker-io/readability-js-server \
--namespace readability \
--create-namespace
```

For local chart development, install from the checkout with `helm install readability-js-server ./charts/readability-js-server`.

Artifact Hub should reference the external Helm repository URL `https://phpdocker-io.github.io/readability-js-server`. It should not be configured to ingest GitHub release assets directly.

## Release and versioning

Docker image publishing remains tag-driven. Bump [`package.json`](package.json), commit it, create the matching `vX.Y.Z` tag, and push the tag to publish the container image and GitHub Release.

Helm chart publishing is separate and runs from chart changes on `master` or an explicit manual trigger. It packages changed charts from `charts/`, updates the GitHub Pages repository on `gh-pages`, and publishes `artifacthub-repo.yml` next to `index.yaml` for Artifact Hub.

For the chart itself:

- Bump `charts/readability-js-server/Chart.yaml` `version` for any chart package change, including templates, defaults, README content, metadata, or publishing annotations.
- Bump `appVersion` only when the chart's default application image tag changes.
- When the default application image tag changes, bump both `version` and `appVersion`.

## Testing

Run the lint and test suites with pnpm:
Expand All @@ -140,6 +168,13 @@ pnpm lint
pnpm test
```

Run the Helm chart checks with the Makefile:

```bash
make helm-lint
make helm-template
```

The repo also exposes a memory soak harness:

```bash
Expand All @@ -155,6 +190,7 @@ The Makefile provides the same checks:
```bash
make lint
make lint-fix
make helm-verify
```

For release tagging there is also:
Expand All @@ -174,9 +210,9 @@ docker run --rm -p 3000:3000 readability-js

The image is based on `node:24-alpine`, installs production dependencies only, and runs the service as a non-root user.

CI on pull requests and pushes to `master` runs lint and tests only. Container publishing happens from the tag-triggered release workflow.
CI on pull requests and pushes to `master` runs lint, tests, and Helm chart verification. Container publishing happens from the tag-triggered release workflow.

For Docker Compose setup, see [`examples/compose.yaml`](examples/compose.yaml).
For Docker Compose setup, see [`examples/compose.yaml`](examples/compose.yaml). That example publishes port `3000` and checks `GET /healthz` from inside the container using the `node` runtime that ships in the published image.

## Security posture

Expand All @@ -193,7 +229,7 @@ This service is still an untrusted content fetcher. Do not relax the defaults wi

## Limits

- Single endpoint only: `POST /`
- Public API endpoints: `POST /` and `GET /healthz`
- No authentication
- No cache
- No persistence
Expand Down
11 changes: 11 additions & 0 deletions artifacthub-repo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Artifact Hub repository metadata for the published Helm repository.
# This file must be served from the same path as index.yaml:
# https://phpdocker-io.github.io/readability-js-server/artifacthub-repo.yml
#
# Add repositoryID after the repository has been created in Artifact Hub to
# enable verified publisher checks.
# repositoryID: 00000000-0000-0000-0000-000000000000

owners:
- name: Luis Pabon
email: luis.pabon@auronconsulting.co.uk
35 changes: 35 additions & 0 deletions charts/readability-js-server/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: v2
name: readability-js-server
description: A production-usable Helm chart for Readability JS Server
type: application
version: 0.1.0
appVersion: "1.8.0"
home: https://github.com/phpdocker-io/readability-js-server
annotations:
artifacthub.io/license: Apache-2.0
artifacthub.io/links: |
- name: source
url: https://github.com/phpdocker-io/readability-js-server
- name: support
url: https://github.com/phpdocker-io/readability-js-server/issues
- name: chart repository
url: https://phpdocker-io.github.io/readability-js-server
artifacthub.io/maintainers: |
- name: Luis Pabon
email: luis.pabon@auronconsulting.co.uk
artifacthub.io/changes: |
- kind: added
description: Publish chart packages and index.yaml to the GitHub Pages Helm repository.
- kind: added
description: Add Artifact Hub repository metadata and installation guidance for the hosted chart repository.
sources:
- https://github.com/phpdocker-io/readability-js-server
keywords:
- readability
- article-extraction
- express
- nodejs
maintainers:
- name: Luis Pabon
email: luis.pabon@auronconsulting.co.uk
url: https://github.com/luispabon
105 changes: 105 additions & 0 deletions charts/readability-js-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# readability-js-server Helm chart

This chart deploys Readability JS Server as a single-replica `Deployment` behind a `ClusterIP` `Service` by default. Optional `Ingress`, `HorizontalPodAutoscaler`, and `PodDisruptionBudget` resources stay disabled until you opt in.

## Prerequisites

- Kubernetes 1.26 or newer
- Helm 3.12 or newer

## Installation

Add the published Helm repository and install the chart:

```bash
helm repo add phpdocker-io https://phpdocker-io.github.io/readability-js-server
helm repo update
helm install readability-js-server phpdocker-io/readability-js-server \
--namespace readability \
--create-namespace
```

For local chart development, install directly from the repository checkout:

```bash
helm install readability-js-server ./charts/readability-js-server
```

Override values at install or upgrade time:

```bash
helm upgrade --install readability-js-server ./charts/readability-js-server \
--namespace readability \
--create-namespace \
--set image.tag=1.8.0 \
--set ingress.enabled=true \
--set ingress.hosts[0].host=readability.example.com
```

## Defaults

- Chart version: `0.1.0`
- App version: `1.8.0`
- Image repository: `phpdockerio/readability-js-server`
- Image tag: `1.8.0`
- Container listen port: `3000`
- Service type: `ClusterIP`
- Replica count: `1`
- Liveness and readiness probes: `GET /healthz`

## Configuration

The chart exposes the settings most clusters need without requiring a fork:

| Area | Values |
| --- | --- |
| Image | `image.repository`, `image.tag`, `image.pullPolicy`, `imagePullSecrets` |
| Service account | `serviceAccount.create`, `serviceAccount.name`, `serviceAccount.annotations`, `serviceAccount.labels`, `serviceAccount.automountServiceAccountToken` |
| Pod metadata | `podLabels`, `podAnnotations`, `priorityClassName` |
| Security | `podSecurityContext`, `containerSecurityContext` |
| Scheduling | `nodeSelector`, `tolerations`, `affinity`, `topologySpreadConstraints` |
| Networking | `service.type`, `service.port`, `service.annotations`, `service.labels`, `ingress.*` |
| Capacity | `replicaCount`, `resources`, `autoscaling.*`, `pdb.*`, `terminationGracePeriodSeconds` |
| Probes | `probes.liveness.*`, `probes.readiness.*`, `probes.startup.*` |
| App config | `appConfig.PORT`, `appConfig.REQUEST_BODY_LIMIT`, `appConfig.FETCH_TIMEOUT_MS`, `appConfig.FETCH_MAX_BYTES`, `appConfig.FETCH_MAX_REDIRECTS`, `appConfig.BLOCK_PRIVATE_NETWORKS`, `appConfig.READABILITY_MAX_ELEMS`, `appConfig.MAX_CONCURRENT_REQUESTS`, `appConfig.CONTENT_FORMAT` |

`appConfig` maps directly to the environment variables already supported by `src/config.js`. `READABILITY_MAX_ELEMS` is unset by default so the service preserves its current runtime behavior until you choose a limit.

## Optional resources

- `ingress.enabled=true` renders a Kubernetes `Ingress`. Configure hostnames, annotations, class name, paths, and TLS through `ingress.*`.
- `autoscaling.enabled=true` renders an `autoscaling/v2` `HorizontalPodAutoscaler`. You can use the built-in CPU and memory targets or provide a full `autoscaling.metrics` list plus `autoscaling.behavior`.
- `pdb.enabled=true` renders a `PodDisruptionBudget`. Set exactly one of `pdb.minAvailable` or `pdb.maxUnavailable`.

## Versioning policy

Chart and application versions are intentionally separate:

- `version` tracks chart package changes. Bump it for any chart content change, including templates, values, `Chart.yaml` metadata, README updates, or Artifact Hub annotations.
- `appVersion` tracks the default Readability JS Server image version used by the chart.

When the chart changes without a new application release, only the chart `version` should move. When the default image tag changes, bump both the chart `version` and `appVersion`.

Docker image publishing remains tag-driven from the root release workflow and follows `package.json`. Helm chart publishing is separate: pushes to `master` that change `charts/**`, plus explicit manual workflow runs, publish the chart repository to GitHub Pages.

## Hosted repository and Artifact Hub

The published Helm repository URL is:

```text
https://phpdocker-io.github.io/readability-js-server
```

Artifact Hub should register that external Helm repository URL. It should not be pointed at GitHub Releases or at the source repository itself.

Repository-level Artifact Hub metadata lives in `artifacthub-repo.yml`. The chart release workflow copies that file onto `gh-pages` so it is served next to `index.yaml`, which is the layout Artifact Hub expects for ownership claims and verified publisher metadata.

## Verification

Render and lint the chart locally:

```bash
helm lint charts/readability-js-server
helm template readability-js-server charts/readability-js-server
helm package charts/readability-js-server
```
18 changes: 18 additions & 0 deletions charts/readability-js-server/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
1. Check the release status:
kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/instance={{ .Release.Name }}"

2. Port-forward the service locally:
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "readability-js-server.fullname" . }} 3000:{{ .Values.service.port }}

3. Probe the service:
curl -i http://127.0.0.1:3000/healthz
{{- if .Values.ingress.enabled }}

4. If your ingress controller is ready, test the configured hostnames:
{{- range .Values.ingress.hosts }}
{{- $host := .host }}
{{- range .paths }}
curl -i http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host }}{{ .path }}
{{- end }}
{{- end }}
{{- end }}
Loading
Loading