Skip to content
Open
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
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# compiled binary
fiche

# default paste output directory
code/

# log files
*.log

# editor artefacts
*.swp
.DS_Store

# environment configuration
.env
.env.example
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Host port exposed for the fiche TCP server
FICHE_PORT=9999

# Host port exposed for the Lines web UI
LINES_PORT=5000

# Domain advertised in paste URLs (e.g. yourdomain.com)
FICHE_DOMAIN=localhost

# Paste storage: named Docker volume (default) or absolute host path for a bind mount
# Named volume example (default): DATA_VOLUME=fiche_data
# Bind-mount example: DATA_VOLUME=/host/path/to/pastes
DATA_VOLUME=fiche_data
123 changes: 123 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Build and Push Docker image to ghcr.io

on:
push:
branches:
- master
tags:
- '*'

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false

- name: Prepare .env
run: cp .env.example .env

- name: Start services
run: docker compose up -d --build

- name: Wait for fiche (TCP 9999)
run: |
echo "Waiting for fiche to accept connections on port 9999..."
for i in $(seq 1 30); do
if nc -z localhost 9999 2>/dev/null; then
echo "fiche is up after ${i}s"
exit 0
fi
sleep 1
done
echo "ERROR: fiche did not become ready in 30 seconds" >&2
exit 1

- name: Wait for lines (HTTP 5000)
run: |
echo "Waiting for lines to respond on port 5000..."
for i in $(seq 1 60); do
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/ || true)
if [ "$status" = "200" ]; then
echo "lines returned HTTP 200 after ${i}s"
exit 0
fi
sleep 1
done
echo "ERROR: lines did not return HTTP 200 in 60 seconds (last status: $status)" >&2
exit 1

- name: Smoke test paste submission
run: |
response=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST http://localhost:5000/submit \
--data-urlencode "content=hello from CI")
if [ "$response" = "200" ] || [ "$response" = "302" ]; then
echo "Paste submission returned HTTP $response – OK"
else
echo "ERROR: unexpected HTTP status $response from /submit" >&2
exit 1
fi

- name: Dump container logs
if: always()
run: docker compose logs --no-color

- name: Tear down
if: always()
run: docker compose down --volumes

build-and-push:
needs: build-and-test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f

- name: Compute image tags
id: image-tags
run: |
image="ghcr.io/${{ github.repository }}"
sha_tag="${image}:${GITHUB_SHA}"
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
{
echo "tags<<EOF"
echo "${sha_tag}"
echo "${image}:${GITHUB_REF_NAME}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
else
{
echo "tags<<EOF"
echo "${sha_tag}"
echo "${image}:latest"
echo "EOF"
} >> "$GITHUB_OUTPUT"
fi

- name: Log in to ghcr.io
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25
with:
context: .
file: Dockerfile
push: true
tags: ${{ steps.image-tags.outputs.tags }}
78 changes: 78 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Docker

on:
push:
pull_request:

jobs:
build-and-test:
name: Build & integration test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

# ── 1. Build the fiche image standalone so build errors are surfaced early ──
- name: Build fiche image
run: docker build --tag fiche:ci .

# ── 2. Provide an env file so docker compose can resolve variables ──
- name: Prepare .env
run: cp .env.example .env

# ── 3. Start the full stack ──
- name: Start services
run: docker compose up -d --build

# ── 4. Wait for the fiche TCP server (port 9999) ──
- name: Wait for fiche (TCP 9999)
run: |
echo "Waiting for fiche to accept connections on port 9999..."
for i in $(seq 1 30); do
if nc -z localhost 9999 2>/dev/null; then
echo "fiche is up after ${i}s"
exit 0
fi
sleep 1
done
echo "ERROR: fiche did not become ready in 30 seconds" >&2
exit 1

# ── 5. Wait for the Lines web UI (port 5000) ──
- name: Wait for lines (HTTP 5000)
run: |
echo "Waiting for lines to respond on port 5000..."
for i in $(seq 1 60); do
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/ || true)
if [ "$status" = "200" ]; then
echo "lines returned HTTP 200 after ${i}s"
exit 0
fi
sleep 1
done
echo "ERROR: lines did not return HTTP 200 in 60 seconds (last status: $status)" >&2
exit 1

# ── 6. Smoke-test: submit a paste via the web UI ──
- name: Smoke test paste submission
run: |
response=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST http://localhost:5000/submit \
--data-urlencode "content=hello from CI")
# Expect 200 (rendered paste) or 302 (redirect to paste URL)
if [ "$response" = "200" ] || [ "$response" = "302" ]; then
echo "Paste submission returned HTTP $response – OK"
else
echo "ERROR: unexpected HTTP status $response from /submit" >&2
exit 1
fi

# ── 7. Always dump logs so failures are easy to diagnose ──
- name: Dump container logs
if: always()
run: docker compose logs --no-color

# ── 8. Tear down ──
- name: Tear down
if: always()
run: docker compose down --volumes
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ code/

# ignore log files
*.log

# local environment configuration (use .env.example as a template)
.env
31 changes: 31 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ---- Build stage ----
FROM ubuntu:24.04 AS builder

RUN apt-get update && \
apt-get install -y --no-install-recommends gcc make libc6-dev && \
rm -rf /var/lib/apt/lists/*

WORKDIR /build
COPY fiche.c fiche.h main.c Makefile ./

RUN make

# ---- Runtime stage ----
FROM ubuntu:24.04

RUN useradd --system --no-create-home --shell /usr/sbin/nologin fiche && \
mkdir -p /data && \
chown fiche:fiche /data

COPY --from=builder /build/fiche /usr/local/bin/fiche

# /data is the directory where pastes are stored.
# Mount a host directory or a named volume here for persistence.
VOLUME /data

USER fiche

EXPOSE 9999

ENTRYPOINT ["fiche"]
CMD ["-o", "/data"]
102 changes: 102 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ You can use our beautification service to get any paste colored and numbered. Ju
http://l.termbin.com/ydxh
```

### Browser-based usage

You can also create and view pastes entirely from a web browser using the bundled `extras/lines` web app (see [Web Interface](#web-interface) below).

-------------------------------------------------------------------------------

## Useful aliases
Expand Down Expand Up @@ -331,6 +335,104 @@ server {
}
```

-------------------------------------------------------------------------------

## Web Interface

The `extras/lines` directory contains a Flask-based web application (`lines.py`) that turns fiche into a **fully browser-usable pastebin**. It provides:

* **A paste-submission form** at `/` — type or paste text, click *Submit*, and receive a shareable URL.
* **Syntax-highlighted paste viewing** at `/<slug>` — any paste (whether submitted through the web form or via `nc`) is displayed with line numbers and automatic language detection, powered by [Pygments](https://pygments.org/).

### Requirements

```
pip install flask pygments
```

### Running

```
python extras/lines/lines.py /path/to/output/directory
```

Then open `http://localhost:5000` in your browser.

> **Note:** The paste *storage* directory must be the same directory used by the fiche TCP server (`-o` flag) if you want both interfaces to share pastes.

-------------------------------------------------------------------------------

## Docker

A `Dockerfile` and `docker-compose.yml` are provided so you can run fiche (and the optional web interface) in containers with data persisted across restarts.

### Quick start with Docker Compose

```bash
# Start both the fiche TCP server (port 9999) and the Lines web UI (port 5000)
docker compose up -d
```

> **Note:** `docker compose` (without a hyphen) requires Docker Engine 20.10+ with the Compose V2 plugin. On older installations use `docker-compose` (with a hyphen) instead.

Paste data is stored in the `fiche_data` named Docker volume and survives container restarts or re-creation.

### Configuration via `.env`

All deployment parameters are controlled by the `.env` file in the project root. Copy the provided template before starting:

```bash
cp .env.example .env
```

Then edit `.env` to customise as needed. The available variables and their defaults are:

| Variable | Default | Description |
|---|---|---|
| `FICHE_PORT` | `9999` | Host port for the fiche TCP server |
| `LINES_PORT` | `5000` | Host port for the Lines web UI |
| `FICHE_DOMAIN` | `localhost` | Domain advertised in paste URLs |
| `DATA_VOLUME` | `fiche_data` | Named Docker volume **or** absolute host path for a bind mount |

Edit `.env` to customise before starting:

```bash
# .env
FICHE_PORT=9999
LINES_PORT=5000
FICHE_DOMAIN=yourdomain.com
DATA_VOLUME=fiche_data # named volume (default)
# DATA_VOLUME=/host/path/pastes # bind-mount alternative
```

Then start:

```bash
docker compose up -d
```

### Accessing pastes

After starting, send text with netcat:

```bash
cat file.txt | nc localhost 9999
```

Open the returned URL in a browser, or browse all pastes through the Lines web UI at `http://localhost:5000`.

### Running with plain Docker

```bash
docker build -t fiche .
docker run -d \
-p 9999:9999 \
-v fiche_data:/data \
fiche -o /data -d yourdomain.com
```

-------------------------------------------------------------------------------

## License

Fiche is MIT licensed.
Loading