This commit is contained in:
Ben Klein 2024-11-22 05:22:06 +00:00 committed by GitHub
commit e7bdf4f273
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 181 additions and 5 deletions

View file

@ -17,12 +17,13 @@ COPY docker-start.sh /
WORKDIR /etc/nginx WORKDIR /etc/nginx
COPY nginx.conf.docker ./nginx.conf COPY nginx.conf.docker ./nginx.conf
COPY metatags.lua .
WORKDIR /var/www WORKDIR /var/www
COPY --from=builder /opt/app/public/ . COPY --from=builder /opt/app/public/ .
FROM nginx:alpine as release FROM openresty/openresty:alpine as release
RUN apk --no-cache add dumb-init RUN apk --no-cache add dumb-init
COPY --from=approot / / COPY --from=approot / /

View file

@ -8,4 +8,5 @@ sed -i "s|__BASEURL__|${BASE_URL:-/}|g" \
/var/www/manifest.json /var/www/manifest.json
# Start server # Start server
exec nginx echo "$0 starting server..."
exec openresty -c /etc/nginx/nginx.conf

View file

@ -22,6 +22,7 @@
<link rel='apple-touch-startup-image' href='img/apple-touch-startup-image-1668x2224.png' media='(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)'/> <link rel='apple-touch-startup-image' href='img/apple-touch-startup-image-1668x2224.png' media='(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)'/>
<link rel='apple-touch-startup-image' href='img/apple-touch-startup-image-2048x2732.png' media='(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)'/> <link rel='apple-touch-startup-image' href='img/apple-touch-startup-image-2048x2732.png' media='(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)'/>
<link rel='manifest' href='manifest.json'/> <link rel='manifest' href='manifest.json'/>
{{ generated_head_tags }}
</head> </head>
<body> <body>
<div id='top-navigation-holder'></div> <div id='top-navigation-holder'></div>

135
client/metatags.lua Normal file
View file

@ -0,0 +1,135 @@
-- Set the response content type back to HTML
ngx.header.content_type = 'text/html';
ngx.req.read_body()
local page_html = ngx.location.capture("/index.htm").body
local before_content, placeholder, after_content
before_content, placeholder, after_content = page_html:match(
string.format("^(.*)(%s)(.*)$", html_head_tag_replacement_str)
)
-- check for placeholder to replace
if not placeholder then
ngx.log(ngx.STDERR, "WARNING: Meta tag placeholder was not found in page html.")
ngx.say(page_html)
return
else
-- start the response
ngx.print(before_content)
-- send partial content to the client to allow preloading the app
ngx.flush()
end
ngx.req.set_header("Accept", "application/json")
local server_info_resp = ngx.location.capture("/_internal_api/info")
if server_info_resp.status ~= 200 then
ngx.log(ngx.STDERR, "Failed to acquire server info from szurubooru API, unable to generate meta tags: HTTP status "..server_info_resp.status)
ngx.print(after_content)
return
end
local server_info = cjson.decode(server_info_resp.body)
local additional_tags = ""
local function add_meta_tag (property, content)
-- NOTE do not allow user-provided data in the property name, only the content has quotes escaped
additional_tags = additional_tags .. "<meta property=\"" .. property .. "\" content=\"" .. tostring(content):gsub('"', '\\"') .. "\"/>"
end
-- Add the site name tag
add_meta_tag("og:site_name", server_info.config.name)
add_meta_tag("og:url", ngx.var.external_host_url .. ngx.var.request_uri_path)
local function generate_meta_tags ()
if ngx.var.request_uri_path:match('^/$') then -- Site root
add_meta_tag("og:type", "website")
add_meta_tag("og:title", server_info.config.name)
add_meta_tag("twitter:title", server_info.config.name)
-- if there's a featured post, let's use that as the image
if server_info.featuredPost then
local post_info = server_info.featuredPost
-- NOTE this is different from the normal Post tags,
-- notably we avoid setting the article type, author, time, etc
local og_media_prefix
if post_info.type == "image" then
og_media_prefix = "og:image"
add_meta_tag("twitter:card", "summary_large_image")
add_meta_tag("twitter:image", ngx.var.external_host_url .. '/' .. post_info.contentUrl)
elseif post_info.type == "video" then
og_media_prefix = "og:video"
-- some sites don't preview video, so at least provide a thumbnail
add_meta_tag("og:image", ngx.var.external_host_url .. '/' .. post_info.thumbnailUrl)
end
add_meta_tag(og_media_prefix..":url", ngx.var.external_host_url .. '/' .. post_info.contentUrl)
add_meta_tag(og_media_prefix..":width", post_info.canvasWidth)
add_meta_tag(og_media_prefix..":height", post_info.canvasHeight)
end
elseif ngx.var.request_uri_path:match('^/post/([0-9]+)') then -- Post metadata
-- check if posts are accessible to anonymous users:
if server_info.config.privileges["posts:view"] == "anonymous" then
local match, err = ngx.re.match(ngx.var.request_uri_path, "^/post/(?<post_id>[0-9]+)")
local post_id = match["post_id"]
add_meta_tag("og:type", "article")
local post_info = cjson.decode((ngx.location.capture("/_internal_api/post/"..post_id)).body)
add_meta_tag("og:title", server_info.config.name .. " - Post " .. post_info.id)
add_meta_tag("twitter:title", server_info.config.name .. " - Post " .. post_info.id)
add_meta_tag("article:published_time", post_info.creationTime)
local og_media_prefix
if post_info.type == "video" then
og_media_prefix = "og:video"
add_meta_tag("twitter:card", "player")
add_meta_tag("twitter:player:width", post_info.canvasWidth)
add_meta_tag("twitter:player:height", post_info.canvasHeight)
-- some sites don't preview video, so at least provide a thumbnail
add_meta_tag("og:image", ngx.var.external_host_url .. '/' .. post_info.thumbnailUrl)
else
og_media_prefix = "og:image"
add_meta_tag("twitter:card", "summary_large_image")
add_meta_tag("twitter:image", ngx.var.external_host_url .. '/' .. post_info.contentUrl)
end
add_meta_tag(og_media_prefix..":url", ngx.var.external_host_url .. '/' .. post_info.contentUrl)
add_meta_tag(og_media_prefix..":width", post_info.canvasWidth)
add_meta_tag(og_media_prefix..":height", post_info.canvasHeight)
-- user is not present for anonymous uploads:
if post_info.user then
add_meta_tag("article:author", post_info.user.name)
end
else
-- no permission to retrieve post data
add_meta_tag("og:title", server_info.config.name .. " - Login required")
end
elseif ngx.var.request_uri_path:match('^/user/([^/]+)/?$') then -- User metadata
local username = ngx.var.request_uri_path:match('^/user/([^/]+)/?$')
-- check for permission to access user profiles
if server_info.config.privileges["users:view"] == "anonymous" then
local user_info_request = ngx.location.capture("/_internal_api/user/"..username)
add_meta_tag("og:type", "profile")
if user_info_request.status == 200 then
add_meta_tag("og:title", server_info.config.name .. " - " .. username)
local user_info = cjson.decode(user_info_request.body)
add_meta_tag("profile:username", user_info.name)
local avatar_url = user_info.avatarUrl
if avatar_url:match("^https?://") then
add_meta_tag("og:image", avatar_url)
else
add_meta_tag("og:image", ngx.var.external_host_url .. '/' .. avatar_url)
end
else
-- could not retrieve the user profile, show some defaults
add_meta_tag("og:title", server_info.config.name .. " - User not found")
end
else
-- no permission to view user data
end
end
end
local status, err = pcall(generate_meta_tags)
if not status then
ngx.log(ngx.STDERR, "Failed to generate meta tags: "..tostring(err))
end
-- Once tags have been generated, write them and then finish the response
ngx.print(additional_tags)
ngx.print(after_content)

View file

@ -1,5 +1,7 @@
worker_processes 1; worker_processes 1;
user nginx; user nobody;
pcre_jit on;
error_log /dev/stderr warn; error_log /dev/stderr warn;
pid /var/run/nginx.pid; pid /var/run/nginx.pid;
@ -9,7 +11,7 @@ events {
} }
http { http {
include /etc/nginx/mime.types; include /usr/local/openresty/nginx/conf/mime.types;
default_type application/octet-stream; default_type application/octet-stream;
log_format main '$remote_addr -> $request [$status] - ' log_format main '$remote_addr -> $request [$status] - '
@ -23,6 +25,15 @@ http {
server __BACKEND__:6666; server __BACKEND__:6666;
} }
map $request_uri $request_uri_path {
"~^(?P<path>[^?]*)(\?.*)?$" $path;
}
init_by_lua_block {
cjson = require("cjson")
html_head_tag_replacement_str = "{{ generated_head_tags }}"
}
server { server {
listen 80 default_server; listen 80 default_server;
@ -50,6 +61,9 @@ http {
gzip_proxied expired no-cache no-store private auth; gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/json; gzip_types text/plain application/json;
proxy_connect_timeout 180s;
proxy_send_timeout 300s;
proxy_read_timeout 600s;
if ($request_uri ~* "/api/(.*)") { if ($request_uri ~* "/api/(.*)") {
proxy_pass http://backend/$1; proxy_pass http://backend/$1;
} }
@ -71,7 +85,7 @@ http {
location / { location / {
root /var/www; root /var/www;
try_files $uri /index.htm; try_files $uri /_meta_tags_html;
sendfile on; sendfile on;
tcp_nopush on; tcp_nopush on;
@ -81,6 +95,30 @@ http {
gzip_proxied expired no-cache no-store private auth; gzip_proxied expired no-cache no-store private auth;
} }
location ~ ^/_internal_api/(.*)$ {
internal;
tcp_nodelay on;
add_header 'Access-Control-Allow-Origin' '*';
gzip off;
proxy_connect_timeout 10s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
proxy_pass http://backend/$1;
}
location /_meta_tags_html {
internal;
set $original_scheme $scheme;
if ( $http_x_forwarded_proto = 'https' ) {
set $original_scheme "https";
}
root /var/www;
set $external_host_url "${original_scheme}://${http_host}";
content_by_lua_file /etc/nginx/metatags.lua;
}
location @unauthorized { location @unauthorized {
return 403 "Unauthorized"; return 403 "Unauthorized";
default_type text/plain; default_type text/plain;