diff options
| -rw-r--r-- | Cargo.toml | 12 | ||||
| -rw-r--r-- | HOWTO.md | 47 | ||||
| -rw-r--r-- | demo.html | 32 | ||||
| -rw-r--r-- | nginx/taar-o.com | 51 | ||||
| -rw-r--r-- | src/main.rs | 72 | ||||
| -rw-r--r-- | static/index.html | 326 |
6 files changed, 540 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a2ab1a7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "skal_server" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.6" +tokio = { version = "1", features = ["full"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +uuid = { version = "1", features = ["v4"] } +tower-http = { version = "0.3", features = ["cors"] } diff --git a/HOWTO.md b/HOWTO.md new file mode 100644 index 0000000..72d1050 --- /dev/null +++ b/HOWTO.md @@ -0,0 +1,47 @@ +# how to use + +PORT=3001 cargo run +curl -X POST "http://127.0.0.1:3001/api/echo?name=Dev" +curl -X GET "http://127.0.0.1:3001/" + +## release + +cargo build --release + +=> binary is at target/release/skal_server +=> test: PORT=3001 ./target/release/skal_server + +=> stop the current service: + +sudo systemctl stop skal-server + +=> update the binary + +sudo cp target/release/skal_server /opt/skal_server/skal_server +sudo cp -R static/*html /opt/skal_server/static +sudo chmod +x /opt/skal_server/skal_server + +=> restart + +sudo systemctl restart skal-server + +=> check status: + +sudo systemctl status skal-server + +=> verify endpoint: + +curl -X POST "https://www.taar-o.com/api/echo?name=ProdTest" + +=> logs + +sudo journalctl -u skal-server -f + +## nginx + +=> config: +/etc/nginx/sites-available/taar-o.com + +=> relaunch +sudo nginx -t +sudo systemctl reload nginx diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..4becabe --- /dev/null +++ b/demo.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Axum Echo</title> +</head> +<body> + <h1>Axum Echo Demo</h1> + + <input id="name" type="text" placeholder="Your name" /> + <button onclick="send()">Send</button> + + <br><br> + <textarea id="output" rows="5" cols="40"></textarea> + + <script> + async function send() { + const name = document.getElementById("name").value; + + const res = await fetch(`/api/echo?name=${encodeURIComponent(name)}`, { + method: "POST" + }); + + const data = await res.json(); + document.getElementById("output").value = + `Hello ${data.name}\nSession ID: ${data.session_id}`; + + localStorage.setItem("session_id", data.session_id); + } + </script> +</body> +</html> diff --git a/nginx/taar-o.com b/nginx/taar-o.com new file mode 100644 index 0000000..998bc9d --- /dev/null +++ b/nginx/taar-o.com @@ -0,0 +1,51 @@ +server { + server_name www.taar-o.com taar-o.com; + + root /var/www/taar-o.com; + index index.html; + + location /.well-known/acme-challenge/ { + alias /var/www/taar-o.com/.well-known/acme-challenge/; + try_files $uri =404; + } + + location / { + try_files $uri $uri/ =404; + } + + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/www.taar-o.com/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/www.taar-o.com/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + location /api/ { + proxy_pass http://127.0.0.1:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + +} +server { + if ($host = taar-o.com) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + if ($host = www.taar-o.com) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + listen 80; + server_name www.taar-o.com taar-o.com; + return 404; # managed by Certbot + + + + +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f0775b5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,72 @@ +use axum::{ + extract::{Query, State}, + response::{Html, IntoResponse}, + routing::{get, post}, + Json, Router, +}; +use serde::Serialize; +use std::fs; +use std::net::SocketAddr; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; +use uuid::Uuid; + +#[derive(Default)] +struct AppState { + sessions: Mutex<HashMap<String, String>>, +} + +#[derive(Serialize)] +struct EchoResponse { + name: String, + session_id: String, +} + +async fn echo( + State(state): State<Arc<AppState>>, + Query(params): Query<HashMap<String, String>>, +) -> Json<EchoResponse> { + let name = params.get("name").cloned().unwrap_or_default(); + let session_id = Uuid::new_v4().to_string(); + + state + .sessions + .lock() + .unwrap() + .insert(session_id.clone(), name.clone()); + + Json(EchoResponse { name, session_id }) +} + +#[tokio::main] +async fn main() { + let state = Arc::new(AppState::default()); + + let port: u16 = std::env::var("PORT") + .unwrap_or_else(|_| "3000".into()) + .parse() + .expect("PORT must be a number"); + + let addr = SocketAddr::from(([127, 0, 0, 1], port)); + + let app = Router::new() + .route( + "/", + get(|| async { + // Serve the default index.html + match fs::read_to_string("static/index.html") { + Ok(html) => Html(html).into_response(), + Err(_) => "Index not found".into_response(), + } + }), + ) + .route("/api/echo", post(echo)) + .with_state(state); + + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..393be43 --- /dev/null +++ b/static/index.html @@ -0,0 +1,326 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Taar-O</title> + <link href="https://fonts.googleapis.com/css2?family=Audiowide&display=swap" rel="stylesheet"> + <style> +* { margin: 0; padding: 0; } +#CANVAS { color:#000; top:0; left:0; position:absolute; width: 100%; height: 100%; } +#FPS { color:#FFF; top:0; left:0; position:absolute; pointer-events:none; padding: 12px; font-family: monospace; } +html, body { width: 100%; height: 100%; overflow: hidden; } + +/* Overlay container */ +#overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + z-index: 10; +} + +/* Logo SVG */ +#logo { + width: 120px; + height: 120px; + margin-bottom: 20px; + animation: float 3s ease-in-out infinite; +} + +/* Main text container */ +#textContainer { + text-align: center; + animation: float 3s ease-in-out infinite; +} + +#mainText { + font-family: 'Audiowide', sans-serif; + font-size: clamp(48px, 12vw, 96px); + font-weight: 700; + background: linear-gradient(135deg, #ff3d00, #ff9100, #ffb300); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin: 0; + letter-spacing: 1px; + filter: drop-shadow(0 0 15px rgba(255, 100, 0, 0.8)) drop-shadow(0 0 30px rgba(255, 50, 0, 0.5)); +} + +#subText { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: clamp(16px, 2.5vw, 24px); + color: rgba(255, 255, 255, 0.7); + margin-top: 16px; + letter-spacing: 1px; + text-transform: uppercase; + font-weight: 300; + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.8)); +} + +/* Floating animation */ +@keyframes float { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-20px); } +} + +/* Bottom left info text */ +#infoText { + position: absolute; + bottom: 30px; + left: 30px; + font-family: monospace; + font-size: 0.9em; + color: rgba(255, 153, 0, 0.7); + line-height: 1.6; + pointer-events: none; + max-width: 300px; +} +</style> +<script type='x-shader/x-fragment' id='h'> +// +// Exploring noise +// +// skal/ (Pascal Massimino) [pascal.massimino@gmail.com] +// License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 +// + +#define eps .01 +#define DMAX 10. +#define MAX_ITER 30 +#define FBM_ITER 8 + +#define R0 0.5 +#define SP0 vec3(.0, .0, .0) +vec3 SP1; + +const vec3 lightDir = normalize(vec3(1., 1., 1.)); +const vec3 lightCol = vec3(.6, .6, .4); + +float gyroid(vec3 p) { + return abs(.05 + dot(sin(p), cos(p.zxy))); +} + +float fbm(vec3 p, float tau) { + float a = tau, v = 0.; + for (int i = 0; i < FBM_ITER; ++i, a *= tau) { + p.z += v * .4; + v += gyroid(p / a) * a; + } + return v; +} + +vec2 testMin(vec2 hit, float d, float id) { + return (d > hit.x) ? hit : vec2(d, id); +} + +vec2 scene(vec3 p) { + vec2 dmin = vec2(DMAX, 0.); + float r0 = R0 + .1 * fbm(p + .1 * vec3(iTime * .4, 0., 0.), .5); + float r1 = .3 + .1 * fbm(p * 3., .3); + dmin = testMin(dmin, length(p - SP0) - r0, 1.); + dmin = testMin(dmin, length(p - SP1) - r1, 2.); + return dmin; +} + +vec3 normal(vec3 p) { + const vec2 EPS = vec2(0., 0.01); +#if 0 + vec3 n = vec3(scene(p + EPS.yxx).x - scene(p - EPS.yxx).x, + scene(p + EPS.xyx).x - scene(p - EPS.xyx).x, + scene(p + EPS.xxy).x - scene(p - EPS.xxy).x); +#else + vec3 n = vec3(scene(p + EPS.yxx).x, scene(p + EPS.xyx).x, scene(p + EPS.xxy).x) - scene(p).xxx; +#endif + return normalize(n); +} + +vec3 trace(vec3 p, vec3 dir) { + float d = eps, hmin = DMAX; + for (int i = 0; d < DMAX && i < MAX_ITER; ++i) { + vec2 h = scene(p + d * dir); + hmin = min(hmin, h.x); + if (abs(h.x) < eps) return vec3(d, h.y, hmin); + d += h.x * .9; + if (d >= DMAX) break; + } + return vec3(DMAX, 0., hmin); // sky +} + +bool shadow(vec3 p, vec3 dir) { + vec3 h = trace(p, dir); + return (h.x < DMAX); +} + +mat3 makeCam(vec3 target, vec3 origin) { + vec3 up = vec3(0., 0., 1.); + vec3 front = normalize(target - origin); + vec3 left = cross(up, front); + up = cross(front, left); + return mat3(left, up, front); +} + +vec3 getColor(vec3 p) { + vec3 c0 = vec3(.2, .6, .7), c1 = vec3(.6, .4, .3); + float t = atan(p.y, p.x); + vec3 c = mix(c0, c1, cos(10. * t + 32. * p.z)); + return c; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = 2. * (fragCoord - iResolution.xy * 0.5) / min(iResolution.x, iResolution.y); + float t = iTime * .4; + SP1 = 1. * cross(vec3(cos(t), sin(t), 0.), normalize(vec3(1., 1., -1.))); + float focal = 1.5; + vec3 target = vec3(0., 0., 0.); + vec3 origin = vec3(2. * cos(t * .3), 2. * sin(t * .3), .5); + mat3 cam = makeCam(target, origin); + vec3 dir = normalize(cam * vec3(uv, focal)); + vec3 h = trace(origin, dir); + vec3 col; + if (h.y > 0.) { + vec3 p = origin + h.x * dir; // hit point + vec3 n = normal(p); + float l = max(0., dot(n, lightDir)); + if (l > 0. && shadow(p + n * 0.01, lightDir)) l *= 0.1; + vec3 base = (h.y == 1.) ? getColor(p) : vec3(.3, .6, .5); + col = base * mix(0.2, 1.0, l); + if (l > 0.) col += lightCol * smoothstep(0.95, 1.0, dot(reflect(dir, n), lightDir)); + } else { // 'sky' + col = vec3(.30, .25, .30) * fbm(dir, .5); // base 'clouds' + col += lightCol * smoothstep(.9, 1., dot(dir, lightDir)); // global diffuse + col = mix(col, vec3(.6, .8, .6), 0.015 / h.z); // halo + } + fragColor = vec4(col, 1.); +} +/////////// END OF THE SHADER CODE PROPER /////////// +</script> + + +<script type='x-shader/x-fragment' id='e'> +uniform int iFrame; +uniform float iTime, iTimeDelta; +uniform vec3 iResolution; +uniform vec4 iMouse; +</script> + +<script> +var last_frame = 0|0, last_time = 0; // for FPS +// uniforms: +var u = { frame_count: last_frame, + time: last_time, + mouse:new Float32Array([0,0,-1,-1]), + screen:new Float32Array([9,6,1]) }; +// global vars: +var cvs, m, prog; +function ge(a) { return document.getElementById(a); } +function gt(a) { return ge(a).innerHTML; } +function ael(n, f) { document.addEventListener(n, f, false); } +function loop() { + cvs.width = window.innerWidth; + cvs.height = window.innerHeight; + const w = u.screen[0] = m.drawingBufferWidth; + const h = u.screen[1] = m.drawingBufferHeight; + m.viewport(0, 0, w, h); + m.scissor(0, 0, w, h); + + function ul(name) { return m.getUniformLocation(prog, name); } + const prev_time = u.time; + u.time = performance.now() * .001; + const dtime = u.time - prev_time; + m.uniform1i(ul("iFrame"), u.frame_count++); + m.uniform1f(ul("iTime"), u.time); + m.uniform1f(ul("iTimeDelta"), dtime); + m.uniform4fv(ul("iMouse"), u.mouse); + m.uniform3fv(ul("iResolution"), u.screen); + + m.drawArrays(m.TRIANGLES, 0, 3); + + const et = u.time - last_time; // compute FPS every ~2 secs + if (et >= 2.) { + const fps = (u.frame_count - last_frame) / et; + ge("FPS").innerHTML = fps.toFixed(1) + " fps"; + last_frame = u.frame_count; + last_time = u.time; + } + requestAnimationFrame(loop); +} + +function go() { + function updateMouse(e) { + u.mouse[0] = e.clientX * u.screen[0] / innerWidth; + u.mouse[1] = u.screen[1] - e.clientY * u.screen[1] / innerHeight; + } + ael('mousemove', e => { if (u.mouse[2] >= 0) updateMouse(e)}); + ael('mousedown', e => { updateMouse(e); if (u.mouse[2] < 0) { u.mouse[2] = u.mouse[0]; u.mouse[3] = u.mouse[1]}}); + ael('mouseup', e => { updateMouse(e); u.mouse[2]=-Math.abs(u.mouse[2]); u.mouse[3]=-Math.abs(u.mouse[3])}); + ael('keydown', e => { }); + + cvs = ge("CANVAS"); + m = cvs.getContext("webgl2", + { alpha:false, depth:false, stencil:false, premultipliedAlpha:false, antialias:false, preserveDrawingBuffer:true }); + const hdr = "#version 300 es\nprecision highp float;\n"; + const vtxSrc = hdr + "\nconst vec2[3] t=vec2[](vec2(3,-1),vec2(-1,-1),vec2(-1,3));void main(){gl_Position=vec4(t[gl_VertexID],0,1);}"; + const frgSrc = hdr + gt('e') + gt('h') + "out vec4 o;void main(){mainImage(o,gl_FragCoord.xy);}"; + + function err(a, b) { alert(a + "\n" + b); return null; } + function compileShader(sh_type, src, name) { + const sh = m.createShader(sh_type); + m.shaderSource(sh, src); + m.compileShader(sh); + if (!m.getShaderParameter(sh, m.COMPILE_STATUS)) + return err(name, m.getShaderInfoLog(sh)); + return sh; + } + const vtx_sh = compileShader(m.VERTEX_SHADER, vtxSrc, 'vtx'); + const frg_sh = compileShader(m.FRAGMENT_SHADER, frgSrc, 'frg'); + prog = m.createProgram(); + m.attachShader(prog, vtx_sh); + m.attachShader(prog, frg_sh); + m.linkProgram(prog); + if (!m.getProgramParameter(prog, m.LINK_STATUS)) + return err('link', m.getProgramInfoLog(prog)); + m.useProgram(prog); + + loop(); // go! +} +</script> +</head> + + +<body onload="go()"> +<canvas id='CANVAS'></canvas> +<div id='FPS'></div> + +<div id='overlay'> + <div> + <svg id='logo' viewBox='0 0 120 120' xmlns='http://www.w3.org/2000/svg'> + <!-- Outer ring --> + <circle cx='60' cy='60' r='55' fill='none' stroke='url(#gradient)' stroke-width='2' opacity='0.8'/> + <!-- Inner circles --> + <circle cx='60' cy='60' r='40' fill='none' stroke='url(#gradient)' stroke-width='1.5' opacity='0.6'/> + <circle cx='60' cy='60' r='25' fill='url(#gradient)' opacity='0.2'/> + <!-- Center point --> + <circle cx='60' cy='60' r='4' fill='url(#gradient)'/> + <defs> + <linearGradient id='gradient' x1='0%' y1='0%' x2='100%' y2='100%'> + <stop offset='0%' style='stop-color:#ff3d00;stop-opacity:1' /> + <stop offset='100%' style='stop-color:#ff9100;stop-opacity:1' /> + </linearGradient> + </defs> + </svg> + <div id='textContainer'> + <h1 id='mainText'>TAAR-O</h1> + <p id='subText'>Exploring Noise</p> + </div> + </div> +</div> +<div id='infoText'> + (c) MASSIMINO Pascal - Metaskal.com +</div> +</body> +</html> |
