mirror of
https://github.com/Myzel394/amiopen.now.git
synced 2025-06-18 07:25:27 +02:00
Merge pull request #1 from Myzel394/add-website
This commit is contained in:
commit
d33324d1e8
29
.github/workflows/release.yaml
vendored
29
.github/workflows/release.yaml
vendored
@ -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 }}
|
||||
|
@ -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
|
||||
|
||||
|
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal 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
23
flake.nix
Normal 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
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
13
src/index.ts
13
src/index.ts
@ -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,
|
||||
|
23
src/middlewares/presentation.ts
Normal file
23
src/middlewares/presentation.ts
Normal 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;
|
@ -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
12
src/routes/root.ts
Normal 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
25
src/utils/renderer.ts
Normal 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
1
static/copy.svg
Normal 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
82
templates/base.html.njk
Normal 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
105
templates/index.html.njk
Normal 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"><port></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"><ip address></span>/<span class="input"><port></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"><port></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
1
templates/index.txt.njk
Normal file
@ -0,0 +1 @@
|
||||
{{ip}}
|
96
templates/port.html.njk
Normal file
96
templates/port.html.njk
Normal 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
1
templates/port.txt.njk
Normal file
@ -0,0 +1 @@
|
||||
{% if isOpen %}open{% else %}closed{% endif %}
|
@ -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"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user