Merge pull request #1 from Myzel394/add-website

This commit is contained in:
Myzel394 2024-11-21 20:37:13 +01:00 committed by GitHub
commit d33324d1e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 473 additions and 33 deletions

View File

@ -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 }}

View File

@ -1,12 +1,10 @@
FROM oven/bun:alpine
FROM oven/bun
WORKDIR /app
COPY package.json ./
COPY bun.lockb ./
COPY src ./src
COPY . /app
RUN bun install
RUN bun install --production
EXPOSE 3000

BIN
bun.lockb

Binary file not shown.

61
flake.lock generated Normal file
View File

@ -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
}

23
flake.nix Normal file
View File

@ -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
];
};
}
);
}

View File

@ -8,11 +8,13 @@
"dependencies": {
"hono": "^4.6.9",
"ip": "^2.0.1",
"nunjucks": "^3.2.4",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/bun": "latest",
"@types/ip": "^1.1.3",
"@types/nunjucks": "^3.2.6",
"prettier": "^3.3.3"
}
}

View File

@ -1,10 +1,17 @@
import { Hono } from "hono";
import { portRoute } from "./routes/port";
import realIP from "./middlewares/real-ip";
import { rootRoute } from "./routes/root";
import { serveStatic } from "hono/bun";
import * as nunjucks from "nunjucks";
const app = new Hono();
nunjucks.configure("templates", {
dev: process.env.NODE_ENV === "development",
});
app.route("/", portRoute);
const app = new Hono()
.use("/static/*", serveStatic({ root: "./" }))
.route("/", rootRoute)
.route("/", portRoute);
Bun.serve({
...app,

View File

@ -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;

View File

@ -1,9 +1,10 @@
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";
import render from "../utils/renderer";
export const portRoute = new Hono();
@ -34,7 +35,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"),
@ -55,12 +56,14 @@ portRoute.get("/:port", realIP, async context => {
const result = await connectToAddress(ip, port, { timeout });
return context.json({
return render(context, "port", {
port: port,
isOpen: result.isOpen,
ip: ip,
});
});
portRoute.get("/:ip/:port", async context => {
portRoute.get("/:ip/:port", realIP, presentation, async context => {
const rawData = {
ip: context.req.param("ip"),
port: context.req.param("port"),
@ -92,7 +95,9 @@ portRoute.get("/:ip/:port", async context => {
const result = await connectToAddress(ip, port, { timeout });
return context.json({
return render(context, "port", {
port: port,
isOpen: result.isOpen,
ip: ip,
});
});

12
src/routes/root.ts Normal file
View File

@ -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"),
});
});

25
src/utils/renderer.ts Normal file
View File

@ -0,0 +1,25 @@
import { Context } from "hono";
import { PresentationType } from "../middlewares/presentation";
import * as nunjucks from "nunjucks";
export default async function render(
context: Context,
templateName: "index" | "port",
ctx: Record<string, any>,
) {
const presentation = context.get("presentation") as PresentationType;
const extension = {
browser: ".html",
terminal: ".txt",
}[presentation];
const key = templateName + extension + ".njk";
const content = nunjucks.render(key, ctx);
switch (presentation) {
case "browser":
return context.html(content);
case "terminal":
return context.text(content);
}
}

1
static/copy.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" /></svg>

After

Width:  |  Height:  |  Size: 201 B

82
templates/base.html.njk Normal file
View File

@ -0,0 +1,82 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="darkreader-lock">
<title>amiopen.now</title>
<style type="text/css">
* {
box-sizing: border-box;
}
body {
font-family: Arial, Helvetica, sans-serif;
background-color: #161318;
color: #eee;
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
}
main {
background-color: #212126;
width: 100%;
max-width: 40em;
margin: 0 auto;
border-radius: 1em;
padding: 3em;
}
h2 {
margin-top: 0;
text-align: left;
}
.socials {
display: flex;
justify-content: end;
column-gap: 1em;
margin-top: 1.4em;
}
.socials img {
color: #fff;
filter: invert(0.7);
border-radius: 50%;
width: 1.4em;
}
.socials img:hover {
filter: invert(1);
}
</style>
{% block header %}{% endblock %}
</head>
<body>
<main>
{% block main %}
<h2>Am I Open</h2>
{% endblock %}
<div class="socials">
<a href="https://github.com/Myzel394/amiopen.now" target="_blank" rel="noopener noreferrer">
<img src="/static/github.svg" alt="GitHub" />
</a>
<a href="https://twitter.com/Myzel394" target="_blank" rel="noopener noreferrer">
<img src="/static/twitter.svg" alt="GitHub" />
</a>
</div>
</main>
{% block scripts %}
{% endblock %}
</body>
</html>

105
templates/index.html.njk Normal file
View File

@ -0,0 +1,105 @@
{% extends "base.html.njk" %}
{% block header %}
<style type="text/css">
code {
background-color: #2f2f2f;
color: #eee;
padding: 1em;
border-radius: 0.5em;
width: 100%;
display: block;
}
code .command {
color: #598eb2;
}
code .input {
color: #b27b59;
}
code .second-input {
color: #b2596e;
}
code .secondary {
color: #b2a559;
}
code .comment {
color: #5c6370;
}
.copy {
cursor: pointer;
border: none;
border-radius: 0.4em;
aspect-ratio: 1;
background: transparent;
vertical-align: middle;
transition: background-color 0.2s;
}
.copy:hover {
background: #ffffff40;
}
.copy img {
width: 1rem;
color: #fff;
filter: invert(1);
}
</style>
{% endblock %}
{% block main %}
<h2>Am I Open</h2>
<p>
Your IP address: <strong>{{ip}}</strong>
<button class="copy" id="copy-ip">
<img width="1em" src="/static/copy.svg" alt="Copy" />
</button>
</p>
<strong> Check if your port is reachable: </strong>
<pre><code><span class="command">curl</span> amiopen.now/<span class="input">&lt;port&gt;</span>
<span class="comment">// Example</span>
<span class="command">curl</span> amiopen.now/<span class="input">80</span>
> open</code></pre>
<strong> Check if an IP address is reachable: </strong>
<pre><code><span class="command">curl</span> amiopen.now/<span class="second-input">&lt;ip address&gt;</span>/<span class="input">&lt;port&gt;</span>
<span class="comment">// Example</span>
<span class="comment">$</span> <span class="command">curl</span> amiopen.now/<span class="second-input">1.1.1.1</span>/<span class="input">53</span>
> open</code></pre>
<strong> Check if your ISP is blocking a port: </strong>
<pre><code><span class="command">telnet</span> amiopen.now <span class="input">&lt;port&gt;</span>
<span class="comment">// Example</span>
<span class="command">telnet</span> amiopen.now <span class="input">80</span></code></pre>
<i>
Hint: You can also check if you can access SSH by using:
<pre><code><span class="command">ssh</span> <span class="secondary">hello</span>@amiopen.now</code></pre>
</i>
{% endblock %}
{% block scripts %}
<script defer>
const $copyButton = document.getElementById("copy-ip");
const ip = "{{ip}}";
copyButton.addEventListener("click", () => {
navigator.clipboard.writeText(ip);
});
</script>
{% endblock %}

1
templates/index.txt.njk Normal file
View File

@ -0,0 +1 @@
{{ip}}

96
templates/port.html.njk Normal file
View File

@ -0,0 +1,96 @@
{% extends "base.html.njk" %}
{% block header %}
<style type="text/css">
.status {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
column-gap: .6em;
font-size: 1.5rem;
}
.status p {
margin: 0;
font-weight: bold;
font-size: 1.2em;
}
#icon {
width: 0.7em;
height: 0.7em;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.status.success {
color: #2ecc71;
}
.status.error {
color: #e74c3c;
}
.status.success #icon {
background-color: #2ecc71;
}
.status.error #icon {
background-color: #e74c3c;
}
.status.success #icon::before {
content: "";
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #2ecc71;
opacity: 0.3;
position: absolute;
top: 0;
left: 0;
transform: scale(2);
animation: pulse 3s ease-out infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 0.6;
}
100% {
transform: scale(2);
opacity: 0;
}
}
</style>
{% endblock %}
{% block main %}
<h2>Am I Open</h2>
<i>{{ ip }}</i>
{% if isOpen %}
<div class="status success">
<div id="icon"></div>
<p>Port <strong>{{port}}</strong> is open</p>
</div>
{% else %}
<div class="status error">
<div id="icon"></div>
<p>Port <strong>{{port}}</strong> is closed</p>
</div>
{% endif %}
{% endblock %}

1
templates/port.txt.njk Normal file
View File

@ -0,0 +1 @@
{% if isOpen %}open{% else %}closed{% endif %}

View File

@ -1,9 +1,8 @@
ip=$1
user=$2
docker buildx build --platform linux/amd64 . --tag amiopen:latest
docker save -o amiopen.tar amiopen:latest
scp amiopen.tar $user@$ip:~/amiopen/amiopen.tar
ssh $user@$ip "cd ~/amiopen && docker container stop amiopen && docker container rm amiopen && docker load -i amiopen.tar && docker compose up -d"
docker buildx build --platform linux/amd64 . --tag amiopen:latest &&
docker save -o amiopen.tar amiopen:latest &&
scp amiopen.tar $user@$ip:~/amiopen/amiopen.tar &&
ssh $user@$ip "cd ~/amiopen && docker container stop amiopen && docker container rm amiopen && docker load -i amiopen.tar && docker compose up -d"