Files
Barcode/README.md
T
2026-09-13 20:05:01 +01:00

386 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.*