From 8ab812dce064191bb6f4212fde64eea9a95766df Mon Sep 17 00:00:00 2001 From: dreamer Date: Thu, 6 Nov 2025 16:37:55 +0100 Subject: [PATCH] start resty port to lapis --- .gitignore | 1 + Dockerfile | 16 ++ code/app.lua | 42 ++++ code/config.lua | 13 ++ code/handlers/roothandler.lua | 27 +++ code/hotmixes/utils.lua | 125 +++++++++++ code/mime.types | 83 +++++++ code/nginx.conf | 45 ++++ code/static/style.css | 410 ++++++++++++++++++++++++++++++++++ code/views/layout.etlua | 35 +++ code/views/root.etlua | 24 ++ docker-compose-local.yaml | 16 ++ docker-compose.yaml | 23 ++ videohotmix.service | 18 ++ 14 files changed, 878 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 code/app.lua create mode 100644 code/config.lua create mode 100644 code/handlers/roothandler.lua create mode 100644 code/hotmixes/utils.lua create mode 100644 code/mime.types create mode 100644 code/nginx.conf create mode 100644 code/static/style.css create mode 100644 code/views/layout.etlua create mode 100644 code/views/root.etlua create mode 100644 docker-compose-local.yaml create mode 100644 docker-compose.yaml create mode 100644 videohotmix.service diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..323f948 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +_data/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9567e04 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM openresty/openresty:1.27.1.2-4-alpine-fat + +ENV LAPIS_VERSION=1.16.0 + +RUN apk add openssl-dev git +RUN apk --no-cache add findutils +RUN apk --no-cache add coreutils +RUN opm get spacewander/luafilesystem +RUN luarocks install luasec +RUN luarocks install busted +RUN luarocks install lapis ${LAPIS_VERSION} +RUN luarocks install moonscript + +WORKDIR /srv/lapis + +CMD lapis server $ENVIRONMENT diff --git a/code/app.lua b/code/app.lua new file mode 100644 index 0000000..5d61139 --- /dev/null +++ b/code/app.lua @@ -0,0 +1,42 @@ +local lapis = require("lapis") +local config = require("lapis.config").get() +local json_params = require("lapis.application").json_params + +local app = lapis.Application() +app:enable("etlua") +app.layout = require "views.layout" + +local autoload = require("lapis.util").autoload +local handlers = autoload("handlers") + +local page_titles = {} + +if string.find(ngx.var.host, "panamaracing.club") then + page_titles = { + name = "Panama Racing Club Archive", + url = "panamaracing.club" + } +elseif string.find(ngx.var.host, "videohotmix.net") then + page_titles = { + name = "Hotmix Video Archive", + url = "videohotmix.net" + } +else + page_titles = { + name = "Local Test Archive", + url = "localhost" + } +end + +app:get("/", function(self) + self.titles = page_titles + return handlers.Roothandler(self) +end) + +app:get("/*", function(self) + self.titles = page_titles + return handlers.Roothandler(self) +end) + + +return app diff --git a/code/config.lua b/code/config.lua new file mode 100644 index 0000000..cc2dc05 --- /dev/null +++ b/code/config.lua @@ -0,0 +1,13 @@ +local config = require("lapis.config") +config({"development", "production"}, { + host = "Video Hotmix Archive", + greeting = "Welcome, We Are Your Friends", + mount = "/mnt/data/", +}) + +config("production", { + greeting = "nothing here yet", + logging = { + queries = false + } +}) diff --git a/code/handlers/roothandler.lua b/code/handlers/roothandler.lua new file mode 100644 index 0000000..fd41169 --- /dev/null +++ b/code/handlers/roothandler.lua @@ -0,0 +1,27 @@ +local to_json = require("lapis.util").to_json +local autoload = require("lapis.util").autoload +local config = require("lapis.config") + +-- print(to_json(config.get())) + +local hotmixes = autoload("hotmixes") + +local function Roothandler(self) + local stuff = hotmixes.utils.these_files( hotmixes.utils.data_path ) + + local latest_path, latest_name = hotmixes.utils.these_latest() + + self.total = hotmixes.utils.total_files_dir( config.get().mount ) + self.uri = hotmixes.utils.request_path + self.path = '/data' .. hotmixes.utils.request_path + self.dirs = stuff.dirs + self.files = stuff.files + self.images = stuff.images + self.latestpath = latest_path + self.latestname = latest_name + + return { render = "root" } + +end + +return Roothandler diff --git a/code/hotmixes/utils.lua b/code/hotmixes/utils.lua new file mode 100644 index 0000000..ad7acba --- /dev/null +++ b/code/hotmixes/utils.lua @@ -0,0 +1,125 @@ +local lfs = require 'lfs_ffi' +local config = require("lapis.config").get() + + +-- setup +local request_uri = ngx.var.request_uri +request_uri = ngx.unescape_uri(request_uri) +ngx.log(ngx.ERR, request_uri) + +if string.find(request_uri, "\"") or string.find(request_uri, "%`") or string.find(request_uri, "%$") then + ngx.log(ngx.ERR, "Illegal request_uri") + ngx.exit(500) +end + +local request_path +if request_uri ~= '/' then + request_path = request_uri .. '/' +else + request_path = request_uri +end + +local data_dir = config.mount +local data_path = data_dir .. request_path +-- setup + + +local utils = {} + +utils['request_path'] = request_path +utils['data_path'] = data_path + +-- we want to know if something is an image +utils['match_image'] = function( file ) + local filext = file:match("[^.]+$") + local extensions = { jpg=true, jpeg=true, png=true, gif=true } + + if extensions[filext:lower()] then + return true + else + return false + end +end + +utils['latest_files'] = function(directory) + local i, t, popen = 0, {}, io.popen + local pfile = popen('find "'..directory..'" -type f ! -name \'*.filepart\' -printf \'%C@ %p\n\'| sort -nr | head -7 | cut -f2- -d" "| sed s:"'..directory..'/"::') + for filename in pfile:lines() do + i = i + 1 + t[i] = filename + end + pfile:close() + return t +end + +utils['these_files'] = function( path ) + local files, dirs, images = {}, {}, {} + for file in lfs.dir( path ) do + if file ~= "." and file ~= ".." and not string.match(file, ".filepart") then + if lfs.attributes( path .. file, "mode" ) == "file" then + if utils.match_image( file ) then + table.insert( images, file ) + else + table.insert( files, file ) + end + elseif lfs.attributes( path .. file, "mode" ) == "directory" then + table.insert( dirs, file ) + end + end + end + + table.sort( images ) + table.sort( files ) + table.sort( dirs ) + + local stuff = { + files = files, + dirs = dirs, + images = images + } + return stuff +end + +utils['these_latest'] = function() + -- list last 10 modified files in our directory + local latest_path, latest_name = {}, {} + + for i, file_path in ipairs( utils.latest_files( data_dir ) ) do + file_path = file_path:gsub( data_dir, "/" ) + table.insert( latest_path, file_path) + + -- local to_json = require("lapis.util").to_json + -- print( to_json(latest_path) ) + + local temp = "" + local result = "" + for i = file_path:len(), 1, -1 do + if file_path:sub(i,i) ~= "/" then + temp = temp..file_path:sub(i,i) + else + break + end + end + + for j = temp:len(), 1, -1 do + result = result..temp:sub(j,j) + end + + table.insert( latest_name, result ) + end + + return latest_path, latest_name +end + +utils['total_files_dir'] = function( path ) + local i, t, popen = 0, {}, io.popen + local pfile = popen('find "'..path..'" -type f | wc -l') + for total in pfile:lines() do + t[i] = total + i = i + 1 + end + pfile:close() + return t +end + +return utils diff --git a/code/mime.types b/code/mime.types new file mode 100644 index 0000000..20a2850 --- /dev/null +++ b/code/mime.types @@ -0,0 +1,83 @@ +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/x-lua lua; + application/x-moonscript moon; + application/x-javascript js; + application/atom+xml atom; + application/rss+xml rss; + application/json json; + application/x-bittorrent torrent; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/code/nginx.conf b/code/nginx.conf new file mode 100644 index 0000000..6107c35 --- /dev/null +++ b/code/nginx.conf @@ -0,0 +1,45 @@ +worker_processes ${{NUM_WORKERS}}; +error_log stderr notice; +daemon off; +pid logs/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include mime.types; + resolver 127.0.0.11; + + init_by_lua_block { + require("socket") + require("lpeg") + math.randomseed(os.time()+ngx.worker.id()) + } + + server { + listen ${{PORT}}; + lua_code_cache ${{CODE_CACHE}}; + + add_header Access-Control-Allow-Origin *; + + location /data/ { + root /mnt; + } + + location /static/ { + alias static/; + } + + location / { + default_type text/html; + content_by_lua_block { + require("lapis").serve("app") + } + } + + location /favicon.ico { + alias static/favicon.ico; + } + } +} diff --git a/code/static/style.css b/code/static/style.css new file mode 100644 index 0000000..e934cef --- /dev/null +++ b/code/static/style.css @@ -0,0 +1,410 @@ +.h1 +{ + font-family: Staatliches; + color: #e2001a; + border-top: 3px solid #e2001a; + border-bottom: 3px solid#e2001a; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 15px; + margin-top: 0px; + line-height: 1em; +} + +.p +{ + font-family: Helvetica; + color: #7e7e7e; + margin-bottom: 45px; + font-size: 10px; +} + +.href +{ + font-family: Helvetica; + text-decoration: none; +} + +.mixlink +{ + line-height: 16px; + font-weight: 600; + font-size: 14px; + margin-bottom: 0; + margin-top: 0; + font-family: Helvetica, Arial, sans-serif; +} + +.amixlink +{ + font-family: Helvetica; + color: #9d9d9d; + line-height: 1.6em; + font-weight: 600; + font-size: 14px; + text-decoration: none; + background: #111111; + display: inline-flex; + margin-top: 2px; + margin-bottom: 2px; + padding: 2px 10px; + border-radius: 20px; +} + +.amixlink:hover +{ + font-family: Helvetica; + color: #000; + line-height: 1.6em; + font-weight: 600; + font-size: 14px; + background: #e2001a; +} + +.body +{ + width: 80%; + padding-left: 10%; + padding-right: 10%; + background-color: #222222; +} + +.mixsections +{ + font-family: Staatliches; + font-weight: 900; + font-size: 16px; + color: #787878; + padding: 5px; + margin-top: 2%; + margin-bottom: 5px; + margin-right: 15px; + border: solid 3px #787878; + text-decoration: none; +} + +.hotmixlogo +{ + width: 150px; + padding-bottom: 0px; + padding-left: 10px; + padding-top: 10px; +} + +.h2 +{ + font-family: Staatliches; + font-size: 24px; + color: #4c4c4c; + font-weight: 200; + border-bottom: 2px solid #4c4c4c; + margin-bottom: 0px; +} + +.header +{ + width: 100%; + background-image: url('/img/header.png'); + max-height: 185px; +} + +.footer +{ + width: 100%; + height: 250px; + border-top: 2px solid #e2001a; + float: left; + clear: both; +} + +.djsection +{ + font-family: Staatliches; + font-size: 18px; + text-decoration: inherit; + color: #e2001a; +} + +.djsectionmenu +{ + font-family: Staatliches; + font-size: 33px; + color: #e2001a; + line-height: 1em; + z-index: 100; + display: block; + width: 10%; + height: 130px; + float: left; + margin-top: 5px; + margin-bottom: 5px; + min-width: 130px; +} + +.djsectionmenu:hover +{ + background-color: #e3301a; +} + +.djsection +{ + font-family: Staatliches; + font-size: 33px; + color: #e2001a; + line-height: 1em; + margin-bottom: 0px; + margin-top: 0px; + padding-top: 5px; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 5px; +} + +.djsection:hover +{ + background-color: #000; +} + +.h2 +{ + font-family: Staatliches; + color: #e2001a; + font-size: 24px; + padding-top: 5px; + border-bottom: 2px solid #e2001a; +} + +.homeheader +{ + background-image: url('/img/header.png'); + background-repeat: no-repeat; + padding-bottom: 20px; +} + +.bottomimg +{ +} + +.imglink +{ + width: 200px; +} + +.footerlegal +{ + right: 0; + float: right; + margin-top: 80px; +} + +/* Three image containers (use 25% for four, and 50% for two, etc) */ + +.column +{ + float: left; + margin-left: 0px; + padding-left: 2.5%; + padding-right: 2.5%; + width: 15%; +} + +/* Clear floats after image containers */ + +.row::after +{ + content: ""; + clear: both; + display: table; +} + +@media screen and (max-width: 768px) +{ + .column + { + padding-left: 5%; + padding-right: 5%; + width: 40%; + } + .h2 + { + font-size: 14px; + } + .footer + { + margin-top: 15%; + } +} + +@media screen and (max-width: 500px) +{ + .column + { + padding-left: 0%; + padding-right: 5%; + width: 45%; + } +} + +.profileimg +{ + overflow: hidden; + height: auto; + width: 30%; +} + +.profilesection +{ + width: 100%; + overflow: hidden; + margin-bottom: 2.5%; + background-color: #e2001a; +} + +.image_collage +{ + line-height: 0; + -webkit-column-count: 5; + -webkit-column-gap: 0px; + -moz-column-count: 5; + -moz-column-gap: 0px; + column-count: 5; + column-gap: 0px; +} + +.image_collage img +{ + width: 100% !important; + height: auto !important; +} + +@media (max-width: 1200px) +{ + .image_collage + { + -moz-column-count: 4; + -webkit-column-count: 4; + column-count: 4; + } +} + +@media (max-width: 1000px) +{ + .image_collage + { + -moz-column-count: 3; + -webkit-column-count: 3; + column-count: 3; + } +} + +@media (max-width: 800px) +{ + .image_collage + { + -moz-column-count: 2; + -webkit-column-count: 2; + column-count: 2; + } +} + +@media (max-width: 400px) +{ + .image_collage + { + -moz-column-count: 1; + -webkit-column-count: 1; + column-count: 1; + } +} + +.alphabetpanel +{ + width: 100%; + float: left; + border-top: 1px dotted; + background-repeat: repeat; + overflow: hidden; + height: 100%; +} + +@media (min-width:500px) +{ + .alphabetimg + { + position: relative; + float: none; + top: 5px; + left: 5px; + height: 100px; + } +} + +.alphabetgrid +{ + width: 100%; + margin-top: 40px; +} + +.latest +{ + position: relative; + clear: both; +} + +.latestdiv +{ + width: 100%; + position: relative; + clear: both; + float: left; +} + +.alphabet +{ + width: 100%; + margin-bottom: 40px; + float: left; + clear: both; + margin-top: 20px; +} + +.sponsors +{ + margin-top: 20px; + clear: both; + float: left; +} + +.linkimg:hover +{ + filter: invert(70%); +} + +@media (max-width:499px) +{ + .djsectionmenu500 + { + height: 70px; + overflow: hidden; + min-width: 60px; + width: 50%; + } + .alphabetimg500 + { + height: 100px; + } + .alphabetpanel480down + { + height: 70px; + overflow: hidden; + width: 100px; + } +} + +@media (max-width:767px) +{ + .h1mobile + { + font-size: 18px; + } +} diff --git a/code/views/layout.etlua b/code/views/layout.etlua new file mode 100644 index 0000000..e47c9ab --- /dev/null +++ b/code/views/layout.etlua @@ -0,0 +1,35 @@ + + + + <%= titles['name'] %> + <% if title then %> + - <%= title %> + <% end %> + + + + + + +

Tons Of Mix - Downloads & Streams (right click + Save link as... for downloads)
+ <%= titles.name %>
+ Sharing <%= total[0] %> files

+ + <% content_for("inner") %> + +
+
+
+
+

Latest uploads:

+ <% for i, file in ipairs(latestpath) do %> + + <%= latestname[i] %> + +
+ <% end %> +
+
+ + + diff --git a/code/views/root.etlua b/code/views/root.etlua new file mode 100644 index 0000000..8a0782b --- /dev/null +++ b/code/views/root.etlua @@ -0,0 +1,24 @@ +
+ +
+<% for i, image in ipairs(images) do %> + + + +<% end %> +
+ +<% for i, dir in ipairs(dirs) do %> +
+ <%= dir %> +
+<% end %> +
+ +<% for i, file in ipairs(files) do %> + + <%= file %> + +
+<% end %> +
diff --git a/docker-compose-local.yaml b/docker-compose-local.yaml new file mode 100644 index 0000000..166c7a6 --- /dev/null +++ b/docker-compose-local.yaml @@ -0,0 +1,16 @@ +services: + videohotmix: + environment: + - ENVIRONMENT + build: . + networks: + - web + volumes: + - ./code:/srv/lapis + - ./_data:/mnt/data + ports: + - "8083:8080" + +networks: + web: + external: true diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..aebfff6 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,23 @@ +services: + videohotmix: + build: . + networks: + - web + volumes: + - /data:/mnt/data:ro + labels: + - traefik.enable=true + - traefik.http.routers.hotmixes.rule=Host(`videohotmix.net`) || Host(`www.videohotmix.net`) || Host(`panamaracing.club`) + - traefik.http.routers.hotmixes.entrypoints=web + - traefik.http.routers.hotmixes.middlewares=redirect-https-hotmixes + - traefik.http.middlewares.redirect-https-hotmixes.redirectscheme.scheme=https + + - traefik.http.routers.hotmixes_ssl.rule=Host(`videohotmix.net`) || Host(`www.videohotmix.net`) || Host(`panamaracing.club`) + - traefik.http.routers.hotmixes_ssl.entrypoints=websecure + - traefik.http.routers.hotmixes_ssl.tls.certresolver=myresolver + + - traefik.http.services.hotmixes.loadbalancer.server.port=80 + +networks: + web: + external: true diff --git a/videohotmix.service b/videohotmix.service new file mode 100644 index 0000000..4e8eee8 --- /dev/null +++ b/videohotmix.service @@ -0,0 +1,18 @@ +[Unit] +Description=Videohotmix service with docker compose +Requires=docker.service +After=docker.service + +[Service] +User=robot +Restart=always +WorkingDirectory=/home/robot/Sources/hotmixes.lapis + +# Compose up +ExecStart=/usr/bin/docker compose up --build + +# Compose down, remove containers +ExecStop=/usr/bin/docker compose down + +[Install] +WantedBy=multi-user.target