Skip to content

Commit f9d6278

Browse files
committed
Improve error verbosity, added ALLOW_UNSAFE_URLS for relative paths, refactor Dockerfile, other enhancements
1 parent 9ee0539 commit f9d6278

6 files changed

Lines changed: 336 additions & 238 deletions

File tree

.github/workflows/docker-image.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@ env:
1111
REGISTRY: ghcr.io
1212
IMAGE_NAME: ${{ github.repository }}
1313

14-
1514
jobs:
16-
build:
17-
15+
test-and-build:
1816
runs-on: ubuntu-latest
1917
permissions:
2018
contents: read
@@ -25,12 +23,14 @@ jobs:
2523
- name: Checkout repository
2624
uses: actions/checkout@v4
2725

28-
- name: Install cosign
29-
if: github.event_name != 'pull_request'
30-
uses: sigstore/cosign-installer@v3.5.0
26+
- name: Set up Go
27+
uses: actions/setup-go@v5
3128
with:
32-
cosign-release: 'v2.2.4'
33-
29+
go-version: '1.24'
30+
31+
- name: Run Tests
32+
run: go test -v ./...
33+
3434
- name: Set up Docker Buildx
3535
uses: docker/setup-buildx-action@v3
3636

Dockerfile

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
FROM golang:alpine AS builder
22

3+
RUN apk add --no-cache ca-certificates
4+
35
WORKDIR /app
46

57
COPY go.mod go.sum ./
68
RUN go mod download
9+
710
COPY . .
811

9-
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o short-it ./cmd/short-it
10-
FROM alpine:latest
12+
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -a -installsuffix cgo -o short-it ./cmd/short-it
1113

12-
RUN apk --no-cache add ca-certificates
14+
FROM scratch
1315

14-
WORKDIR /root/
16+
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
1517

16-
COPY --from=builder /app/short-it .
17-
RUN mkdir -p /data
18+
COPY --from=builder /app/short-it /short-it
1819

1920
EXPOSE 8080
2021
EXPOSE 8090
2122

2223
ENV DB_PATH=/data/short-it.db
2324

24-
CMD ["./short-it"]
25+
CMD ["/short-it"]

README.md

Lines changed: 71 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@ A minimal, self-hosted URL shortener written in Go using bbolt (BoltDB). This pr
44

55
## Features
66

7-
- Shorten URLs with autogenerated 6-character keys
8-
- Create custom paths
9-
- List stored URLs with cursor-based pagination
10-
- Simple token-based authorization
11-
- Single-file embedded database (bbolt)
12-
- Tracking analytics with Rybbit (other providers soon to come)
7+
* Shorten URLs with autogenerated 6-character keys
8+
9+
* Create custom paths (or relative links with safe-mode disabled)
10+
11+
* List stored URLs with cursor-based pagination
12+
13+
* Unified API and redirect listener
14+
15+
* Built-in lightweight, simple web UI (optional)
16+
17+
* Simple token-based authorization for operations
18+
19+
* Single-file embedded database (bbolt)
20+
21+
* Tracking analytics with Rybbit (more soon), natively integrated (optional)
1322

1423
## Quick Start
1524

@@ -18,115 +27,103 @@ Prerequisites: Go 1.24 or Docker.
1827
Run locally:
1928

2029
```bash
21-
go build -o short-it ./cmd/short-it
22-
APP_TOKEN=your-secret-token DB_PATH=short-it.db API_PORT=8080 ./short-it
30+
go build -o short-it .
31+
APP_TOKEN=your-secret-token API_PORT=8080 WEB_UI=true ./short-it
2332
```
2433

2534
Run with Docker:
2635

2736
```bash
2837
docker build -t short-it .
29-
docker run -e APP_TOKEN=your-secret-token -p 8080:8080 -v $(pwd)/data:/data short-it
38+
docker run -e APP_TOKEN=your-secret-token -e WEB_UI=true -p 8080:8080 -p 8090:8090 -v $(pwd)/data:/data short-it
3039
```
3140

32-
Or use the provided docker-compose configuration:
41+
Or use the provided `docker-compose.yml` configuration:
3342

34-
```bash
43+
```yaml
3544
docker-compose up -d
3645
# edit docker-compose.yml to set APP_TOKEN or update the environment
3746
```
3847

39-
The server listens on `API_PORT` (default `8080`) and stores the BoltDB file at `DB_PATH` (default `short-it.db`).
48+
The primary API and redirect server listens on `API_PORT` (default `8080`). If enabled, the Web UI listens on `WEB_UI_PORT` (default `8090`).
49+
The BoltDB file is stored at `./short-it.db` by default, or `/data/short-it.db` in Docker configurations.
4050

4151
Or deploy it in one click:
4252

4353
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/UaONmD?referralCode=Zw7fpP&utm_medium=integration&utm_source=template&utm_campaign=generic)
4454

55+
> **Note:** Ensure you update the environment variables to match the latest version when deploying.
56+
4557
## Configuration
4658

4759
Environment variables:
4860

49-
- `APP_TOKEN` (required): token used for authorization on protected endpoints
50-
- `API_PORT` (optional): HTTP port (default `8080`)
51-
- `API_URL` (optional): URL to access the API (default `http://localhost:8080`)
52-
- `DB_PATH` (optional): path to BoltDB file (default `short-it.db`)
53-
- `WEB_UI` (optional): set to `true` to host a small link-creation webpage
54-
- `WEB_UI_URL` (optional): URL to access the web UI (default `http://localhost:8090`)
55-
- `WEB_UI_PORT` (optional): port for the web UI server (default `8080`)
56-
- `RYBBIT_SITE_ID` (optional): Site ID provided by Rybbit
57-
- `RYBBIT_SITE_KEY` (optional): API key found in account settings for Rybbit
58-
- `RYBBIT_SITE_URL` (optional): Base URL for your Rybbit instance
61+
### Core Setup
5962

60-
Notes for `WEB_UI`:
63+
* `APP_TOKEN` (required): Token used for authorization on protected API endpoints.
6164

62-
- The API server still listens on `API_PORT`.
63-
- The web UI starts only when `WEB_UI=true`.
64-
- If `WEB_UI_PORT` is the same value as `API_PORT`, the UI is skipped to avoid interfering with the API process.
65+
* `API_PORT` (optional): Port for the main API and URL redirection (default `8080`).
6566

66-
## HTTP API
67+
* `API_URL` (optional): Explicit base URL used for generating shortened links in the Web UI. Intelligently inferred if omitted.
6768

68-
All endpoints that modify or list data require the `Authorization` header to equal the `APP_TOKEN` value.
69+
* `DB_PATH` (optional): Filepath for the BoltDB database (default `short-it.db`).
6970

70-
- Create a short URL
71-
- `POST /`
72-
- Headers: `Authorization`, `URL` (or JSON body `{ "url": "..." }`)
73-
- Response: `{"key":"<generated-key>"}`
71+
* `ALLOW_UNSAFE_URLS` (optional): Set to `true` to allow relative paths (e.g., `/relative`) or unsupported schemas (default `false`).
7472

75-
- List URLs (paginated)
76-
- `GET /`
77-
- Headers: `Authorization`, optional `Cursor`, optional `Limit` (max 100)
78-
- Response: `{ "items": [{"key":"...","value":"..."}], "next":"<cursor>" }`
73+
### Web UI Settings
7974

80-
- Redirect
81-
- `GET /{key}`
82-
- Redirects (302) to the stored URL if found
75+
* `WEB_UI` (optional): Set to `true` to enable the built-in HTML creation page (default `false`).
8376

84-
- Create/Update custom path
85-
- `PUT /{path}`
86-
- Headers: `Authorization`, `URL` (or JSON body)
87-
- Response: `201 Created`
77+
* `WEB_UI_PORT` (optional): Port for the Web UI (default `8090`).
8878

89-
- Delete path
90-
- `DELETE /{path}`
91-
- Headers: `Authorization`
92-
- Response: `204 No Content`
79+
### Rybbit Analytics (Optional)
9380

94-
Examples:
81+
* `RYBBIT_SITE_ID`: Your Rybbit Site ID.
9582

96-
```bash
97-
# shorten a URL (using header)
98-
curl -X POST -H "Authorization: your-secret-token" -H "URL: https://example.com" http://localhost:8080/
83+
* `RYBBIT_SITE_KEY`: Your Rybbit API Key.
9984

100-
# get redirect
101-
curl -v http://localhost:8080/:key
85+
* `RYBBIT_SITE_URL`: The URL to your Rybbit instance.
10286

103-
# create custom path
104-
curl -X PUT -H "Authorization: your-secret-token" -H "URL: https://custom.example" http://localhost:8080/custom
87+
## HTTP API
10588

106-
# delete
107-
curl -X DELETE -H "Authorization: your-secret-token" http://localhost:8080/custom
89+
All management endpoints require the `Authorization` header to equal the `APP_TOKEN` value.
10890

109-
# list
110-
curl -H "Authorization: your-secret-token" http://localhost:8080/
111-
```
91+
Base URL: `http://localhost:8080` (or your `API_PORT` value)
11292

113-
## Development & Tests
93+
* **Create a short URL**
94+
* `POST /`
95+
* Headers: `Authorization`, `URL` (or JSON body `{ "url": "..." }`)
96+
* Response: `{"key":"<generated-key>"}`
11497

115-
Run unit tests:
98+
* **List URLs (paginated)**
99+
* `GET /`
100+
* Headers: `Authorization`, optional `Cursor`, optional `Limit` (default max 100, or unlimited with 0)
101+
* Response: `{ "items": [{"key":"...","value":"..."}], "next":"<cursor>" }`
116102

117-
```bash
118-
go test ./cmd/short-it
119-
```
103+
* **Create/Update custom path**
104+
* `PUT /{path}`
105+
* Headers: `Authorization`, `URL` (or JSON body `{ "url": "..." }`)
106+
* Response: `201 Created`
107+
108+
* **Delete path**
109+
* `DELETE /{path}`
110+
* Headers: `Authorization`
111+
* Response: `204 No Content`
112+
113+
* **Redirect (Public)**
114+
* `GET /{key}`
115+
* Redirects (302 Found) to the stored URL if it exists.
116+
117+
## Web UI
118+
119+
If `WEB_UI=true` is set, a web interface is exposed on `http://localhost:8090` (or `WEB_UI_PORT`).
120+
121+
* `GET /`: Serves the HTML interface to easily create short URLs.
120122

121-
Key implementation files:
123+
* `POST /api/create`: Internal endpoint used by the Web UI form.
122124

123-
- [cmd/short-it/main.go](cmd/short-it/main.go#L1) — HTTP handlers and core logic
124-
- [cmd/short-it/main_test.go](cmd/short-it/main_test.go#L1) — unit tests for handlers and DB ops
125-
- [Dockerfile](Dockerfile) — container build
126-
- [docker-compose.yml](docker-compose.yml) — example compose configuration
125+
### Notes
127126

128-
## Notes
127+
* The app uses a BoltDB bucket named `urls` to store key → URL mappings.
129128

130-
- The app uses a BoltDB bucket named `urls` to store key → URL mappings.
131-
- The autogenerated keys are 6 characters drawn from `a-zA-Z0-9`.
132-
- Sends pageview events to Rybbit if configured with the hostname, language, pathname, user-agent, referrer, and IP address gathered from `X-Forwarded-For`.
129+
* The autogenerated keys are 6 characters drawn from `a-zA-Z0-9`.

0 commit comments

Comments
 (0)