This commit is contained in:
commit
9389fd5f8c
15 changed files with 418 additions and 0 deletions
12
.editorconfig
Normal file
12
.editorconfig
Normal 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
11
.env.example
Normal 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
|
24
.forgejo/workflows/biomejs.yml
Normal file
24
.forgejo/workflows/biomejs.yml
Normal 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
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/node_modules
|
||||||
|
bun.lock
|
||||||
|
.env
|
37
Dockerfile
Normal file
37
Dockerfile
Normal 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
28
LICENSE
Normal 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
70
README.md
Normal 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
34
biome.json
Normal 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
16
compose.yml
Normal 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
44
config/environment.ts
Normal 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
24
package.json
Normal 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
76
src/index.ts
Normal 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
33
tsconfig.json
Normal 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
5
types/config.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
type Environment = {
|
||||||
|
port: number;
|
||||||
|
host: string;
|
||||||
|
development: boolean;
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue