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