first commit
All checks were successful
Code quality checks / biome (push) Successful in 7s

This commit is contained in:
creations 2025-05-11 14:07:03 -04:00
commit 9389fd5f8c
Signed by: creations
GPG key ID: 8F553AA4320FC711
15 changed files with 418 additions and 0 deletions

12
.editorconfig Normal file
View file

@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

11
.env.example Normal file
View file

@ -0,0 +1,11 @@
# Server configuration
HOST=0.0.0.0
PORT=8080
# Forgejo instance settings
FORGEJO_URL=https://git.example.com
FORGEJO_TOKEN=your_forgejo_api_token_here
# Repository branch and name to serve static content from
BRANCH=static-pages
REPO=pages

View file

@ -0,0 +1,24 @@
name: Code quality checks
on:
push:
pull_request:
jobs:
biome:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Bun
run: |
curl -fsSL https://bun.sh/install | bash
export BUN_INSTALL="$HOME/.bun"
echo "$BUN_INSTALL/bin" >> $GITHUB_PATH
- name: Install Dependencies
run: bun install
- name: Run Biome with verbose output
run: bunx biome ci . --verbose

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/node_modules
bun.lock
.env

37
Dockerfile Normal file
View file

@ -0,0 +1,37 @@
# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:latest AS base
WORKDIR /usr/src/app
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
# [optional] tests & build
ENV NODE_ENV=production
# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/src ./src
COPY --from=prerelease /usr/src/app/package.json .
COPY --from=prerelease /usr/src/app/tsconfig.json .
COPY --from=prerelease /usr/src/app/config ./config
COPY --from=prerelease /usr/src/app/types ./types
RUN mkdir -p /usr/src/app/logs && chown bun:bun /usr/src/app/logs
USER bun
ENTRYPOINT [ "bun", "run", "start" ]

28
LICENSE Normal file
View file

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2025, [fullname]
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

70
README.md Normal file
View file

@ -0,0 +1,70 @@
# Bun Forgejo Pages Proxy
A simple static page server built with Bun that proxies files from a Forgejo repository.
## Features
- Serves static files (HTML, CSS, JS, images, etc.) from a `static-pages` branch
- Automatic `index.html` resolution for directory paths
- Proper MIME type handling via `mime` package
- Minimal and fast Bun server
## Requirements
- [Bun](https://bun.sh/) >= 1.2
- A Forgejo instance with an API token
- Repositories with a `static-pages` branch (or any configured via `BRANCH`)
## Setup
### 1. Clone the repository
```bash
git clone https://git.creations.works/creations/forgejoPages
cd forgejoPages
```
### 2. Install dependencies
```bash
bun install
```
### 3. Configure environment
Copy `.env.example` to `.env` and fill in your Forgejo details:
```bash
cp .env.example .env
```
### 4. Start the server
```bash
bun run dev
```
## Environment Variables
| Name | Description |
|-----------------|-----------------------------------------------------|
| `HOST` | Host to bind the server to |
| `PORT` | Port to run the server on |
| `FORGEJO_URL` | URL to your Forgejo instance |
| `FORGEJO_TOKEN` | Personal access token with repo read access |
| `BRANCH` | Branch to serve files from (e.g., `static-pages`) |
| `REPO` | Repository name to use (defaults to `pages`) |
## How It Works
Requesting `/username/path/to/file` will fetch:
```
https://FORGEJO_URL/api/v1/repos/username/pages/raw/path/to/file?ref=BRANCH
```
If a request ends in `/`, it automatically appends `index.html`.
## License
[MIT](LICENSE)

34
biome.json Normal file
View file

@ -0,0 +1,34 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": true
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"lineEnding": "lf"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"indentStyle": "tab",
"lineEnding": "lf",
"jsxQuoteStyle": "double",
"semicolons": "always"
}
}
}

16
compose.yml Normal file
View file

@ -0,0 +1,16 @@
services:
profile-page:
container_name: forgejoPages
build:
context: .
restart: unless-stopped
ports:
- "${PORT:-6600}:${PORT:-6600}"
env_file:
- .env
networks:
- forgejoPages-network
networks:
forgejoPages-network:
driver: bridge

44
config/environment.ts Normal file
View file

@ -0,0 +1,44 @@
import { logger } from "@creations.works/logger";
const environment: Environment = {
port: Number.parseInt(process.env.PORT || "8080", 10),
host: process.env.HOST || "0.0.0.0",
development:
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
};
const forgejo = {
url: process.env.FORGEJO_URL || "",
token: process.env.FORGEJO_TOKEN || "",
branch: process.env.BRANCH || "static-pages",
repo: process.env.REPO || "pages",
};
function verifyRequiredVariables(): void {
const requiredVariables = [
"HOST",
"PORT",
"FORGEJO_URL",
"FORGEJO_TOKEN",
"BRANCH",
"REPO",
];
let hasError = false;
for (const key of requiredVariables) {
const value = process.env[key];
if (value === undefined || value.trim() === "") {
logger.error(`Missing or empty environment variable: ${key}`);
hasError = true;
}
}
if (hasError) {
process.exit(1);
}
}
export { environment, forgejo, verifyRequiredVariables };

24
package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "forgejo_pages",
"module": "src/index.ts",
"type": "module",
"scripts": {
"start": "bun run src/index.ts",
"dev": "bun run --hot src/index.ts --dev",
"lint": "bunx biome check",
"lint:fix": "bunx biome check --fix",
"cleanup": "rm -rf logs node_modules bun.lockdb"
},
"devDependencies": {
"@types/bun": "^1.2.13",
"globals": "^16.1.0",
"@biomejs/biome": "^1.9.4"
},
"peerDependencies": {
"typescript": "^5.8.3"
},
"dependencies": {
"@creations.works/logger": "^1.0.3",
"mime": "^4.0.7"
}
}

76
src/index.ts Normal file
View file

@ -0,0 +1,76 @@
import {
environment,
forgejo,
verifyRequiredVariables,
} from "@config/environment";
import { logger } from "@creations.works/logger";
import { fetch, serve } from "bun";
import mime from "mime";
async function main(): Promise<void> {
verifyRequiredVariables();
serve({
port: environment.port,
hostname: environment.host,
async fetch(req) {
const url = new URL(req.url);
const parts = url.pathname.split("/").filter(Boolean);
if (parts.length < 1) {
return new Response("Not found", { status: 404 });
}
// Redirect /username → /username/ to make relative paths work -_-
if (
parts.length === 1 &&
!url.pathname.endsWith("/") &&
!url.pathname.includes(".")
) {
return Response.redirect(`${url.pathname}/`, 301);
}
const username = parts[0];
if (!username.match(/^[a-z0-9_-]+$/i)) {
return new Response("Not found", { status: 404 });
}
let filePath = parts.slice(1).join("/");
if (url.pathname.endsWith("/") || filePath === "") {
filePath += "index.html";
}
const apiUrl = `${forgejo.url}/api/v1/repos/${username}/${forgejo.repo}/raw/${filePath}?ref=${forgejo.branch}`;
logger.info(`Proxying: ${url.pathname}${apiUrl}`);
const res = await fetch(apiUrl, {
headers: {
Authorization: `token ${forgejo.token}`,
},
});
if (!res.ok) {
return new Response("File not found", { status: res.status });
}
const contentType = mime.getType(filePath) || "application/octet-stream";
return new Response(res.body, {
status: res.status,
headers: {
"Content-Type": contentType,
},
});
},
});
logger.info(
`Server running at http://${environment.host}:${environment.port}`,
);
}
main().catch((error: Error) => {
logger.error(["Error initializing the server:", error]);
process.exit(1);
});

33
tsconfig.json Normal file
View file

@ -0,0 +1,33 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@config/*": ["config/*"],
"@types/*": ["types/*"],
"@helpers/*": ["src/helpers/*"]
},
"typeRoots": ["./src/types", "./node_modules/@types"],
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
},
"include": ["src", "types", "config"]
}

5
types/config.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
type Environment = {
port: number;
host: string;
development: boolean;
};