diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 26bdbaa..221efb6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,22 +1,21 @@ name: Build and release on: - release: - types: [published] + release: + types: [published] jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 - - name: Add SSH identity key - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts - - - name: Build & upload image - run: ./upload_to_server.sh ${{ secrets.SERVER_IP }} ${{ secrets.SSH_USER }} + - name: Add SSH identity key + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts + - name: Build & upload image + run: ./upload_to_server.sh ${{ secrets.SERVER_IP }} ${{ secrets.SSH_USER }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d0a9adb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM oven/bun + +WORKDIR /app + +COPY . /app + +# Absolutely no idea why, but piping the output directly to the file makes it empty. +RUN mkdir -p /tmp/templates && mv /app/templates/*.html /tmp/templates/ +RUN bunx html-minifier --collapse-whitespace --remove-comments --remove-optional-tags --remove-redundant-attributes --remove-script-type-attributes --remove-tag-whitespace --use-short-doctype --minify-css true --minify-js true /tmp/templates/index.html > /app/templates/index.html +RUN bun install --production + +EXPOSE 3000 + +ENTRYPOINT ["bun", "run", "start"] + diff --git a/server/bun.lockb b/bun.lockb similarity index 98% rename from server/bun.lockb rename to bun.lockb index 79d48b7..5b268d4 100755 Binary files a/server/bun.lockb and b/bun.lockb differ diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..73fe5a1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1731676054, + "narHash": "sha256-OZiZ3m8SCMfh3B6bfGC/Bm4x3qc1m2SVEAlkV6iY7Yg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "5e4fbfb6b3de1aa2872b76d49fafc942626e2add", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..08ade3a --- /dev/null +++ b/flake.nix @@ -0,0 +1,23 @@ +{ + description = "A very basic flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, utils }: utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + html-minifier + bun + vscode-langservers-extracted + ]; + }; + } + ); +} diff --git a/server/package.json b/package.json similarity index 100% rename from server/package.json rename to package.json diff --git a/server/Dockerfile b/server/Dockerfile deleted file mode 100644 index 012f915..0000000 --- a/server/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM oven/bun:alpine - -WORKDIR /app - -COPY package.json ./ -COPY bun.lockb ./ -COPY src ./src - -RUN bun install - -EXPOSE 3000 - -ENTRYPOINT ["bun", "run", "start"] - diff --git a/server/src/index.ts b/server/src/index.ts deleted file mode 100644 index fd389f5..0000000 --- a/server/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Hono } from "hono"; -import { portRoute } from "./routes/port"; -import realIP from "./middlewares/real-ip"; - -const app = new Hono(); - -app.route("/", portRoute); - -Bun.serve({ - ...app, - idleTimeout: 90, -}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1556924 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,14 @@ +import { Hono } from "hono"; +import { portRoute } from "./routes/port"; +import { rootRoute } from "./routes/root"; +import { serveStatic } from "hono/bun"; + +const app = new Hono() + .use("/static/*", serveStatic({ root: "./" })) + .route("/", rootRoute) + .route("/", portRoute); + +Bun.serve({ + ...app, + idleTimeout: 90, +}); diff --git a/src/middlewares/presentation.ts b/src/middlewares/presentation.ts new file mode 100644 index 0000000..3792a6f --- /dev/null +++ b/src/middlewares/presentation.ts @@ -0,0 +1,23 @@ +import { createMiddleware } from "hono/factory"; + +export type PresentationType = "terminal" | "browser"; + +const TERMINAL_USER_AGENT = /^(curl|wget|python-urllib|pycurl|java)/i; + +const presentation = createMiddleware<{ + Variables: { + presentation: PresentationType; + }; +}>(async (context, next) => { + const userAgent = context.req.header("User-Agent") || ""; + + if (TERMINAL_USER_AGENT.test(userAgent)) { + context.set("presentation", "terminal"); + } else { + context.set("presentation", "browser"); + } + + await next(); +}); + +export default presentation; diff --git a/server/src/middlewares/real-ip.ts b/src/middlewares/real-ip.ts similarity index 100% rename from server/src/middlewares/real-ip.ts rename to src/middlewares/real-ip.ts diff --git a/server/src/routes/port.ts b/src/routes/port.ts similarity index 93% rename from server/src/routes/port.ts rename to src/routes/port.ts index 5b15585..942ba1b 100644 --- a/server/src/routes/port.ts +++ b/src/routes/port.ts @@ -1,9 +1,9 @@ import { Hono } from "hono"; -import { getConnInfo } from "hono/bun"; import connectToAddress from "../utils/connect-to-address"; import { z } from "zod"; import * as IP from "ip"; import realIP from "../middlewares/real-ip"; +import presentation from "../middlewares/presentation"; export const portRoute = new Hono(); @@ -34,7 +34,7 @@ const schema = z.object({ .pipe(z.number().min(100).max(60_000)), }); -portRoute.get("/:port", realIP, async context => { +portRoute.get("/:port", realIP, presentation, async context => { const rawData = { ip: context.get("ip"), port: context.req.param("port"), diff --git a/src/routes/root.ts b/src/routes/root.ts new file mode 100644 index 0000000..dbb78bb --- /dev/null +++ b/src/routes/root.ts @@ -0,0 +1,12 @@ +import { Hono } from "hono"; +import presentation from "../middlewares/presentation"; +import realIP from "../middlewares/real-ip"; +import render from "../utils/renderer"; + +export const rootRoute = new Hono(); + +rootRoute.get("/", realIP, presentation, context => { + return render(context, "index", { + ip: context.get("ip"), + }); +}); diff --git a/server/src/utils/connect-to-address.ts b/src/utils/connect-to-address.ts similarity index 100% rename from server/src/utils/connect-to-address.ts rename to src/utils/connect-to-address.ts diff --git a/src/utils/renderer.ts b/src/utils/renderer.ts new file mode 100644 index 0000000..ff75bb3 --- /dev/null +++ b/src/utils/renderer.ts @@ -0,0 +1,46 @@ +import { Context } from "hono"; +import { PresentationType } from "../middlewares/presentation"; + +export default async function render( + request: Context, + templateName: "index", + ctx: Record, +) { + const presentation = request.get("presentation"); + let content = await getTemplate(templateName, presentation); + + for (const [key, value] of Object.entries(ctx)) { + content = content.replaceAll(`{{${key}}}`, value); + } + + switch (presentation) { + case "browser": { + return request.html(content); + } + case "terminal": { + return request.text(content); + } + } +} + +const _templateCache: Record = {}; + +async function getTemplate( + templateName: "index", + presentation: PresentationType, +): Promise { + const extension = { + browser: ".html", + terminal: ".txt", + }[presentation]; + const key = templateName + extension; + + if (_templateCache[key]) { + return _templateCache[key]; + } + + const currentPath = await Bun.resolve(`./templates/${key}`, process.cwd()); + _templateCache[key] = await Bun.file(currentPath).text(); + + return _templateCache[key]; +} diff --git a/server/src/utils/timeout.ts b/src/utils/timeout.ts similarity index 100% rename from server/src/utils/timeout.ts rename to src/utils/timeout.ts diff --git a/static/copy.svg b/static/copy.svg new file mode 100644 index 0000000..b4e4725 --- /dev/null +++ b/static/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..a7bbe4a --- /dev/null +++ b/templates/index.html @@ -0,0 +1,140 @@ + + + + + + + amiopen.now + + + + +
+

Am I Open

+

+ Your IP address: {{ip}} + +

+ + Check if your port is reachable: +
curl amiopen.now/<port>
+
+// Example
+curl amiopen.now/80
+> open
+ + Check if an IP address is reachable: +
curl amiopen.now/<ip address>/<port>
+
+// Example
+$ curl amiopen.now/1.1.1.1/53
+> open
+ + Check if your ISP is blocking a port: +
telnet amiopen.now <port>
+
+// Example
+telnet amiopen.now 80
+ + + Hint: You can also check if you can access SSH by using: +
ssh hello@amiopen.now
+
+
+ + + diff --git a/templates/index.txt b/templates/index.txt new file mode 100644 index 0000000..9315426 --- /dev/null +++ b/templates/index.txt @@ -0,0 +1 @@ +{{ip}} diff --git a/server/tsconfig.json b/tsconfig.json similarity index 100% rename from server/tsconfig.json rename to tsconfig.json