Files
Barcode/README.md
T

386 lines
15 KiB
Markdown
Raw Normal View History

2026-09-13 20:05:01 +01:00
# Cipher · Barcode Studio
A self-contained, browser-based barcode generator that converts text or numeric sequences into 22 different barcode symbologies and exports them as downloadable JPG (or PNG) images. Designed to run as a single hardened Docker container with no backend, no database, and no telemetry — everything happens in the user's browser.
![status](https://img.shields.io/badge/status-stable-success) ![docker](https://img.shields.io/badge/docker-compose-blue) ![license](https://img.shields.io/badge/license-MIT-lightgrey)
---
## Table of Contents
1. [Features](#features)
2. [Supported Barcode Formats](#supported-barcode-formats)
3. [Architecture](#architecture)
4. [Requirements](#requirements)
5. [Quick Start](#quick-start)
6. [Configuration](#configuration)
7. [Project Structure](#project-structure)
8. [Security](#security)
9. [Dependency Versions & Vulnerability Status](#dependency-versions--vulnerability-status)
10. [Operations](#operations)
11. [Updating & Maintenance](#updating--maintenance)
12. [Troubleshooting](#troubleshooting)
13. [Development](#development)
14. [License](#license)
---
## Features
* **22 barcode symbologies** spanning retail/GS1, linear/industrial, pharma, postal and 2D matrix codes.
* **Live example preview** — every symbology shows a sample render the moment it's selected.
* **Batch generation** — paste lines into the input area and each line becomes its own barcode card.
* **Per-card text toggle** — show or hide the human-readable caption beneath each barcode, then download it in that state.
* **JPG and PNG export** — individual downloads per barcode plus a one-click "Download All as JPG" for the whole batch.
* **Adjustable scale and JPG quality** for print-ready output.
* **Fully offline-capable** once loaded — every library is vendored locally, no third-party CDN at runtime (web fonts are the only external request and are optional).
* **No backend** — runs entirely as static assets served by nginx. No database, no API, no user data leaves the browser.
---
## Supported Barcode Formats
| Group | Symbology | Notes |
|---|---|---|
| Retail · GS1 | EAN-13, EAN-8, UPC-A, UPC-E, ISBN-13, GS1 DataBar, GS1-128 | ISBN-10 auto-converts to ISBN-13 |
| Linear · Industrial | Code-128, Code-39, Code-39 Full ASCII, Code-93, Code-11, Code 2of5 Interleaved, MSI Plessey, Flattermarken, Telepen Alpha | |
| Pharma · Specialty | Pharmacode One-Track, Pharmacode Two-Track | Laetus binary codes |
| Postal | KIX | Dutch PostNL routing |
| 2D · Matrix | QR Code, Data Matrix, PDF417 | |
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌───────────────────────────────────────────────────┐ │
│ │ index.html (UI + state) │ │
│ │ ├── JsBarcode (linear codes) │ │
│ │ └── bwip-js (specialty + 2D codes) │ │
│ │ ─→ HTMLCanvasElement → JPG / PNG Blob │ │
│ └───────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────┘
│ HTTP (static assets only)
┌─────────────────────────────────────────────────────────────┐
│ Docker container · nginx 1.30.1 · alpine 3.23 │
│ • runs as non-root (UID 101) │
│ • read-only root filesystem │
│ • all caps dropped, no-new-privileges │
│ • CSP / HSTS / nosniff / X-Frame-Options / COOP / CORP │
└─────────────────────────────────────────────────────────────┘
```
Encoding happens **entirely in the browser** — the container only ships static HTML/JS. The image cannot leak data because there is no application server to leak through.
---
## Requirements
| Component | Minimum version | Why |
|---|---|---|
| Docker Engine | **24.0+** | Buildkit, compose-v2 features |
| Docker Compose | **v2.20+** | Modern compose schema (`deploy.resources`, `tmpfs` options) |
| Host OS | Linux / macOS / Windows (WSL2) | Anything Docker supports |
| RAM | 64 MB free | Container reserves 32 MB, caps at 128 MB |
| Disk | ~50 MB | Image is ~40 MB |
| Open port | one host port (default 8080) | Reconfigurable via `.env` |
Verify your toolchain:
```bash
docker --version # → Docker version 24.x.x or newer
docker compose version # → Docker Compose version v2.20+ or newer
```
If you're on an older Docker that only has the standalone `docker-compose` binary, the file still works — just replace `docker compose` with `docker-compose` in the commands below.
---
## Quick Start
```bash
# 1. Unzip and enter the project
unzip cipher-barcode-studio.zip
cd cipher-barcode-studio
# 2. (Optional) override the host port
cp .env.example .env # then edit HOST_PORT if needed
# 3. Build and start
docker compose up -d --build
# 4. Open in browser
# http://localhost:8080
```
That's it. To stop:
```bash
docker compose down
```
To stop and remove the built image as well:
```bash
docker compose down --rmi local
```
---
## Configuration
All runtime configuration lives in `.env` (copied from `.env.example`):
| Variable | Default | Description |
|---|---|---|
| `HOST_PORT` | `8080` | Port published on the host machine |
The container itself always listens on `8080` internally — only the host-side port is configurable.
### Putting it behind a reverse proxy
If you're running this behind Traefik, Caddy, nginx, or similar:
```yaml
# example with Traefik labels
services:
cipher:
# ...existing config...
labels:
- "traefik.enable=true"
- "traefik.http.routers.cipher.rule=Host(`barcode.example.com`)"
- "traefik.http.routers.cipher.tls=true"
- "traefik.http.routers.cipher.tls.certresolver=letsencrypt"
- "traefik.http.services.cipher.loadbalancer.server.port=8080"
```
When fronted by HTTPS, the `Strict-Transport-Security` header in `nginx.conf` will kick in automatically.
---
## Project Structure
```
cipher-barcode-studio/
├── docker-compose.yml # Orchestration + runtime hardening
├── Dockerfile # Image build steps
├── .dockerignore # Keep the build context lean
├── .env.example # Template for runtime config
├── LICENSE # MIT + third-party attributions
├── README.md # You are here
├── nginx/
│ └── nginx.conf # Hardened nginx config w/ CSP & security headers
└── web/
├── index.html # The full application (single HTML file)
└── vendor/
├── JsBarcode.all.min.js
└── bwip-js-min.js
```
---
## Security
This image is built with a defense-in-depth posture even though it serves static content with no user data:
### Container hardening
| Control | Mechanism |
|---|---|
| Non-root execution | Image's `USER` is `nginx` (UID 101). Compose pins `user: "101:101"`. |
| Read-only rootfs | `read_only: true` in compose; writable paths exposed via `tmpfs`. |
| Capability drop | `cap_drop: [ALL]` — no Linux capabilities granted. |
| No privilege escalation | `security_opt: no-new-privileges:true`. |
| Resource limits | CPU capped at 0.5 cores, memory at 128 MB. |
| Minimal base | `nginxinc/nginx-unprivileged:alpine3.23-slim` — Alpine, no extra packages, security upgrades applied at build. |
| Restricted log size | json-file driver capped at 5 MB × 3 rotations. |
### HTTP hardening (in `nginx/nginx.conf`)
| Header | Purpose |
|---|---|
| `Content-Security-Policy` | Restricts scripts/styles/fonts/images to `self` + Google Fonts only |
| `Strict-Transport-Security` | One-year HSTS with subdomain coverage (effective behind TLS) |
| `X-Frame-Options: DENY` + CSP `frame-ancestors 'none'` | Clickjacking protection |
| `X-Content-Type-Options: nosniff` | Disables MIME sniffing |
| `Referrer-Policy: strict-origin-when-cross-origin` | Limits referrer leakage |
| `Permissions-Policy` | Disables camera, mic, geolocation, USB, payment APIs |
| `Cross-Origin-Opener-Policy: same-origin` | Window isolation |
| `Cross-Origin-Resource-Policy: same-origin` | Resource isolation |
| `server_tokens off` | Hides nginx version |
Also: request size capped at 1 KB (this is a static GET-only service), hidden files (`/\.`) blocked, `/healthz` lightweight endpoint for orchestrators.
### Supply chain
* JavaScript libraries are **vendored** under `web/vendor/`, not loaded from CDN at runtime.
* Each `<script>` tag in `index.html` carries a **Subresource Integrity (SRI)** hash (`sha384`), so any tampering with the vendor files will cause the browser to refuse execution.
* The only runtime third-party connection is to Google Fonts for typography. If you need full air-gap operation, see [Removing Google Fonts](#removing-google-fonts).
### Scanning the image yourself
```bash
docker compose build
docker scout cves cipher-barcode-studio:1.0.0 # native Docker scanner
# or
trivy image cipher-barcode-studio:1.0.0 # Trivy
# or
grype cipher-barcode-studio:1.0.0 # Grype
```
Expect findings to be limited to base-image OS packages; the application itself has no Node.js, npm, or other runtime dependencies to scan.
---
## Dependency Versions & Vulnerability Status
All versions verified against published CVE feeds at build time.
| Component | Pinned version | Latest as of build | Known CVEs | Source of truth |
|---|---|---|---|---|
| nginx | `1.30.1` | 1.30.1 (stable) | none active | [nginx security advisories](https://nginx.org/en/security_advisories.html) |
| Alpine Linux | `3.23` | 3.23 | base image kept patched via `apk upgrade` in Dockerfile | [Alpine secdb](https://secdb.alpinelinux.org/) |
| JsBarcode | `3.12.3` | 3.12.3 | 0 | [Snyk advisor](https://snyk.io/advisor/npm-package/jsbarcode), [Socket](https://socket.dev/npm/package/jsbarcode) |
| bwip-js | `4.10.1` | 4.10.1 | 0 | [Snyk advisor](https://security.snyk.io/package/npm/bwip-js) |
The build pulls `nginxinc/nginx-unprivileged:1.30.1-alpine3.23-slim` and then runs `apk upgrade --no-cache` so any Alpine package CVEs disclosed after the image was published are still patched.
If you fork this repo, re-run the vulnerability check periodically:
```bash
docker compose build --no-cache
trivy image --severity HIGH,CRITICAL cipher-barcode-studio:1.0.0
```
---
## Operations
### View logs
```bash
docker compose logs -f cipher
```
### Healthcheck
```bash
docker inspect --format='{{json .State.Health}}' cipher-barcode-studio | jq
# or hit it directly:
curl -i http://localhost:8080/healthz
```
### Restart
```bash
docker compose restart cipher
```
### Inspect security posture of the running container
```bash
docker inspect cipher-barcode-studio | jq '.[0].HostConfig | {ReadonlyRootfs, CapDrop, SecurityOpt, Memory, NanoCpus}'
```
Expected output should include `"ReadonlyRootfs": true`, `"CapDrop": ["ALL"]`, and `"no-new-privileges:true"`.
---
## Updating & Maintenance
### Rebuild with the latest patches
```bash
docker compose build --no-cache --pull
docker compose up -d
```
`--pull` forces Docker to refetch the base image, ensuring you pick up new Alpine and nginx releases.
### Update a vendored JS library
```bash
cd web/vendor
# Example: bump bwip-js
curl -sSL -o bwip-js-min.js "https://cdn.jsdelivr.net/npm/bwip-js@<new-version>/dist/bwip-js-min.js"
# Recompute the SRI hash
openssl dgst -sha384 -binary bwip-js-min.js | openssl base64 -A | xargs -I{} echo "sha384-{}"
# Paste the new hash into the integrity="" attribute in web/index.html
# Rebuild
docker compose up -d --build
```
### Removing Google Fonts
If you need a fully air-gapped install, edit `web/index.html`:
1. Delete the three `<link rel="preconnect">` and `<link rel="stylesheet">` tags pointing at `fonts.googleapis.com` / `fonts.gstatic.com`.
2. The page still works — it just falls back to the next font in each `font-family` stack (`serif` / `monospace`).
3. Tighten `nginx/nginx.conf` by removing the `https://fonts.googleapis.com` and `https://fonts.gstatic.com` entries from the CSP.
---
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| `bind: address already in use` | Port 8080 is taken | Set `HOST_PORT=8081` (or any free port) in `.env` and `docker compose up -d` |
| Container restart-loops with "Permission denied" on `/var/cache/nginx` | Older Docker without tmpfs option support | Upgrade Docker Engine to 24+ |
| Browser shows blank page, console says "Refused to execute script ... integrity" | Vendor file modified locally without updating SRI | Recompute SRI hash (see [Updating](#update-a-vendored-js-library)) |
| Fonts look generic | Network blocks Google Fonts | Either allow `fonts.googleapis.com` / `fonts.gstatic.com` or follow [Removing Google Fonts](#removing-google-fonts) |
| QR / Data Matrix barcode renders but caption is cut off | Output is a 2D code with long text | Hide the text via the per-card "Hide text" button, or shorten the input |
| `docker compose` not found | Only the legacy standalone `docker-compose` is installed | Use `docker-compose up -d --build` instead; or install the v2 plugin |
### Resetting from scratch
```bash
docker compose down --rmi local --volumes
docker compose up -d --build --force-recreate
```
---
## Development
To iterate on the UI without rebuilding the image every time, mount `web/` over the container's webroot:
```yaml
# docker-compose.override.yml (gitignored convention)
services:
cipher:
volumes:
- ./web:/usr/share/nginx/html:ro
```
Then:
```bash
docker compose up -d
# edit web/index.html, refresh the browser — no rebuild needed
```
When you're done, delete the override file and rebuild for production.
---
## License
This project is released under the **MIT License** — see [LICENSE](./LICENSE).
Third-party libraries retain their own licenses:
* **JsBarcode** — MIT — https://github.com/lindell/JsBarcode
* **bwip-js** — MIT — https://github.com/metafloor/bwip-js
* **nginx** — BSD-2-Clause — https://nginx.org/LICENSE
* **Alpine Linux** — MIT-equivalent — https://alpinelinux.org/
---
*Built with care. Encode anything. Print everything.*