mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
2 Commits
7227ac2b1b
...
master
Author | SHA1 | Date | |
---|---|---|---|
f66a8d2a96 | |||
ee7e9ef2a3 |
@ -1,4 +1,5 @@
|
||||
node_modules/*
|
||||
public/
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
**/.gitignore
|
||||
|
@ -1,3 +1,26 @@
|
||||
FROM --platform=$BUILDPLATFORM node:lts-alpine as development
|
||||
WORKDIR /opt/app
|
||||
|
||||
RUN apk --no-cache add \
|
||||
dumb-init \
|
||||
nginx \
|
||||
git
|
||||
|
||||
RUN ln -sf /opt/app/nginx.conf.docker /etc/nginx/nginx.conf
|
||||
RUN rm -rf /var/www
|
||||
RUN ln -sf /opt/app/public/ /var/www
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
ARG BUILD_INFO="docker-development"
|
||||
ENV BUILD_INFO=${BUILD_INFO}
|
||||
ENV BACKEND_HOST="server"
|
||||
|
||||
CMD ["/opt/app/docker-start-dev.sh"]
|
||||
VOLUME ["/data"]
|
||||
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:lts as builder
|
||||
WORKDIR /opt/app
|
||||
|
||||
|
@ -315,7 +315,7 @@ function makeOutputDirs() {
|
||||
}
|
||||
|
||||
function watch() {
|
||||
let wss = new WebSocket.Server({ port: 8080 });
|
||||
let wss = new WebSocket.Server({ port: environment === "development" ? 8081 : 8080 });
|
||||
const liveReload = !process.argv.includes('--no-live-reload');
|
||||
|
||||
function emitReload() {
|
||||
|
@ -121,6 +121,7 @@
|
||||
.thumbnail
|
||||
width: 4em
|
||||
height: 3em
|
||||
background-position: 50% 30%
|
||||
li
|
||||
margin: 0 0.3em 0.3em 0
|
||||
display: inline-block
|
||||
|
17
client/docker-start-dev.sh
Executable file
17
client/docker-start-dev.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/dumb-init /bin/sh
|
||||
|
||||
# Integrate environment variables
|
||||
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
|
||||
/etc/nginx/nginx.conf
|
||||
|
||||
# Start server
|
||||
nginx &
|
||||
|
||||
# Watch source for changes and build app
|
||||
# FIXME: It's not ergonomic to run `npm i` outside of the build step.
|
||||
# However, the mounting of different directories into the
|
||||
# client container's /opt/app causes node_modules to disappear
|
||||
# (the mounting causes client/Dockerfile's RUN npm install
|
||||
# to silently clobber).
|
||||
# Find a way to move `npm i` into client/Dockerfile.
|
||||
npm i && npm run watch -- --polling
|
@ -2,10 +2,10 @@
|
||||
|
||||
# Integrate environment variables
|
||||
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
|
||||
/etc/nginx/nginx.conf
|
||||
/etc/nginx/nginx.conf
|
||||
sed -i "s|__BASEURL__|${BASE_URL:-/}|g" \
|
||||
/var/www/index.htm \
|
||||
/var/www/manifest.json
|
||||
/var/www/index.htm \
|
||||
/var/www/manifest.json
|
||||
|
||||
# Start server
|
||||
exec nginx
|
||||
|
@ -3,7 +3,7 @@
|
||||
const config = require("./config.js");
|
||||
|
||||
if (config.environment == "development") {
|
||||
var ws = new WebSocket("ws://" + location.hostname + ":8080");
|
||||
var ws = new WebSocket("ws://" + location.hostname + ":8081");
|
||||
ws.addEventListener("open", function (event) {
|
||||
console.log("Live-reloading websocket connected.");
|
||||
});
|
||||
|
@ -10,10 +10,6 @@ BUILD_INFO=latest
|
||||
# otherwise the port specified here will be publicly accessible
|
||||
PORT=8080
|
||||
|
||||
# Uncomment PORT_SERVER variable if you did the same in docker-compose.yml
|
||||
# to make embeds work.
|
||||
# PORT_SERVER=8081
|
||||
|
||||
# How many waitress threads to start
|
||||
# 4 is the default amount of threads. If you experience performance
|
||||
# degradation with a large number of posts, increasing this may
|
||||
|
54
docker-compose.dev.yml
Normal file
54
docker-compose.dev.yml
Normal file
@ -0,0 +1,54 @@
|
||||
## Docker Compose configuration for dev iteration
|
||||
##
|
||||
## Data is transient by using named vols.
|
||||
## Run: docker-compose -f ./docker-compose.dev.yml up
|
||||
services:
|
||||
|
||||
server:
|
||||
build:
|
||||
context: ./server
|
||||
target: development
|
||||
depends_on:
|
||||
- sql
|
||||
environment:
|
||||
## These should be the names of the dependent containers listed below,
|
||||
## or FQDNs/IP addresses if these services are running outside of Docker
|
||||
POSTGRES_HOST: sql
|
||||
## Credentials for database:
|
||||
POSTGRES_USER:
|
||||
POSTGRES_PASSWORD:
|
||||
## Commented Values are Default:
|
||||
#POSTGRES_DB: defaults to same as POSTGRES_USER
|
||||
#POSTGRES_PORT: 5432
|
||||
#LOG_SQL: 0 (1 for verbose SQL logs)
|
||||
THREADS:
|
||||
volumes:
|
||||
- "data:/data"
|
||||
- "./server/:/opt/app/"
|
||||
|
||||
client:
|
||||
build:
|
||||
context: ./client
|
||||
target: development
|
||||
depends_on:
|
||||
- server
|
||||
volumes:
|
||||
- "data:/data:ro"
|
||||
- "./client/:/opt/app/"
|
||||
- "/opt/app/public/"
|
||||
ports:
|
||||
- "${PORT}:80"
|
||||
- "8081:8081"
|
||||
|
||||
sql:
|
||||
image: postgres:11-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER:
|
||||
POSTGRES_PASSWORD:
|
||||
volumes:
|
||||
- "sql:/var/lib/postgresql/data"
|
||||
|
||||
volumes:
|
||||
data:
|
||||
sql:
|
@ -21,12 +21,8 @@ services:
|
||||
#LOG_SQL: 0 (1 for verbose SQL logs)
|
||||
THREADS:
|
||||
volumes:
|
||||
- client:/opt/app/client:ro
|
||||
- "${MOUNT_DATA}:/data"
|
||||
- "./server/config.yaml:/opt/app/config.yaml"
|
||||
## Expose this port if you want embeds
|
||||
#ports:
|
||||
# - "${PORT_SERVER}:6666"
|
||||
|
||||
client:
|
||||
image: szurubooru/client:latest
|
||||
@ -36,7 +32,6 @@ services:
|
||||
BACKEND_HOST: server
|
||||
BASE_URL:
|
||||
volumes:
|
||||
- client:/var/www
|
||||
- "${MOUNT_DATA}:/data:ro"
|
||||
ports:
|
||||
- "${PORT}:80"
|
||||
@ -49,6 +44,3 @@ services:
|
||||
POSTGRES_PASSWORD:
|
||||
volumes:
|
||||
- "${MOUNT_SQL}:/var/lib/postgresql/data"
|
||||
|
||||
volumes:
|
||||
client:
|
||||
|
@ -61,7 +61,42 @@ ENTRYPOINT ["pytest", "--tb=short"]
|
||||
CMD ["szurubooru/"]
|
||||
|
||||
|
||||
FROM prereqs as development
|
||||
WORKDIR /opt/app
|
||||
|
||||
ARG PUID=1000
|
||||
ARG PGID=1000
|
||||
|
||||
RUN apk --no-cache add \
|
||||
dumb-init \
|
||||
py3-pip \
|
||||
py3-setuptools \
|
||||
py3-waitress \
|
||||
&& pip3 install --no-cache-dir --disable-pip-version-check \
|
||||
hupper \
|
||||
&& mkdir -p /opt/app /data \
|
||||
&& addgroup -g ${PGID} app \
|
||||
&& adduser -SDH -h /opt/app -g '' -G app -u ${PUID} app \
|
||||
&& chown -R app:app /opt/app /data
|
||||
|
||||
USER app
|
||||
CMD ["/opt/app/docker-start-dev.sh"]
|
||||
|
||||
ARG PORT=6666
|
||||
ENV PORT=${PORT}
|
||||
EXPOSE ${PORT}
|
||||
|
||||
ARG THREADS=4
|
||||
ENV THREADS=${THREADS}
|
||||
|
||||
VOLUME ["/data/"]
|
||||
|
||||
|
||||
FROM prereqs as release
|
||||
|
||||
COPY ./ /opt/app/
|
||||
RUN rm -rf /opt/app/szurubooru/tests
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
ARG PUID=1000
|
||||
|
@ -169,19 +169,11 @@ privileges:
|
||||
'uploads:create': regular
|
||||
'uploads:use_downloader': power
|
||||
|
||||
homepage_url: https://www.example.com/
|
||||
site_url: https://www.example.com/booru
|
||||
|
||||
## Client folder sharing, required for embeds.
|
||||
# Docker requires you to share /var/www from the client to the server.
|
||||
client_dir: /opt/app/client
|
||||
|
||||
## ONLY SET THESE IF DEPLOYING OUTSIDE OF DOCKER
|
||||
#debug: 0 # generate server logs?
|
||||
#show_sql: 0 # show sql in server logs?
|
||||
#data_url: /data/
|
||||
#data_dir: /var/www/data
|
||||
#client_dir: /var/www
|
||||
## usage: schema://user:password@host:port/database_name
|
||||
## example: postgres://szuru:dog@localhost:5432/szuru_test
|
||||
#database:
|
||||
|
8
server/docker-start-dev.sh
Executable file
8
server/docker-start-dev.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/dumb-init /bin/sh
|
||||
set -e
|
||||
cd /opt/app
|
||||
|
||||
alembic upgrade head
|
||||
|
||||
echo "Starting szurubooru API on port ${PORT} - Running on ${THREADS} threads"
|
||||
exec hupper -m waitress --port ${PORT} --threads ${THREADS} szurubooru.facade:app
|
@ -1,5 +1,4 @@
|
||||
import szurubooru.api.comment_api
|
||||
import szurubooru.api.embed_api
|
||||
import szurubooru.api.info_api
|
||||
import szurubooru.api.password_reset_api
|
||||
import szurubooru.api.pool_api
|
||||
|
@ -1,111 +0,0 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
import html
|
||||
from urllib.parse import quote
|
||||
from typing import Dict, Optional
|
||||
|
||||
from szurubooru import config, model, rest
|
||||
from szurubooru.func import (
|
||||
auth,
|
||||
posts,
|
||||
serialization,
|
||||
)
|
||||
|
||||
if (Path(config.config['client_dir']) / "index.htm").exists():
|
||||
with open(f"{config.config['client_dir']}/index.htm") as index:
|
||||
index_html = index.read()
|
||||
else:
|
||||
logging.warning("Could not find index.htm needed for embeds.")
|
||||
|
||||
def _index_path(params: Dict[str, str]) -> int:
|
||||
try:
|
||||
return params["path"]
|
||||
except (TypeError, ValueError):
|
||||
raise posts.InvalidPostIdError(
|
||||
"Invalid post ID."
|
||||
)
|
||||
|
||||
|
||||
def _get_post(post_id: int) -> model.Post:
|
||||
return posts.get_post_by_id(post_id)
|
||||
|
||||
|
||||
def _get_post_id(match: re.Match) -> int:
|
||||
post_id = match.group("post_id")
|
||||
try:
|
||||
return int(post_id)
|
||||
except (TypeError, ValueError):
|
||||
raise posts.InvalidPostIdError(
|
||||
"Invalid post ID: %r." % post_id
|
||||
)
|
||||
|
||||
|
||||
def _serialize_post(
|
||||
ctx: rest.Context, post: Optional[model.Post]
|
||||
) -> rest.Response:
|
||||
return posts.serialize_post(
|
||||
post, ctx.user, options=["thumbnailUrl", "user"]
|
||||
)
|
||||
|
||||
|
||||
@rest.routes.get("/oembed/?")
|
||||
def get_post(
|
||||
ctx: rest.Context, _params: Dict[str, str] = {}, url: str = ""
|
||||
) -> rest.Response:
|
||||
auth.verify_privilege(ctx.user, "posts:view")
|
||||
|
||||
url = url or ctx.get_param_as_string("url")
|
||||
match = re.match(r".*?/post/(?P<post_id>\d+)", url)
|
||||
if not match:
|
||||
raise posts.InvalidPostIdError("Invalid post ID.")
|
||||
|
||||
post_id = _get_post_id(match)
|
||||
post = _get_post(post_id)
|
||||
serialized = _serialize_post(ctx, post)
|
||||
embed = {
|
||||
"version": "1.0",
|
||||
"type": "photo",
|
||||
"title": f"{config.config['name']} – Post #{post_id}",
|
||||
"author_name": serialized["user"]["name"] if serialized["user"] else None,
|
||||
"provider_name": config.config["name"],
|
||||
"provider_url": config.config["homepage_url"],
|
||||
"thumbnail_url": f"{config.config['site_url']}/{serialized['thumbnailUrl']}",
|
||||
"thumbnail_width": int(config.config["thumbnails"]["post_width"]),
|
||||
"thumbnail_height": int(config.config["thumbnails"]["post_height"]),
|
||||
"url": f"{config.config['site_url']}/{serialized['thumbnailUrl']}",
|
||||
"width": int(config.config["thumbnails"]["post_width"]),
|
||||
"height": int(config.config["thumbnails"]["post_height"])
|
||||
}
|
||||
return embed
|
||||
|
||||
|
||||
@rest.routes.get("/index(?P<path>/.+)")
|
||||
def post_index(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
|
||||
path = _index_path(params)
|
||||
|
||||
if not index_html:
|
||||
logging.info("Embed was requested but index.htm file does not exist. Redirecting to 404.")
|
||||
return {"return_type": "custom", "status_code": "404", "content": [("content-type", "text/html")]}
|
||||
|
||||
try:
|
||||
oembed = get_post(ctx, {}, path)
|
||||
except posts.PostNotFoundError:
|
||||
return {"return_type": "custom", "status_code": "404", "content": index_html}
|
||||
|
||||
url = config.config["site_url"] + path
|
||||
new_html = index_html.replace("</head>", f'''
|
||||
<meta property="og:site_name" content="{config.config["name"]}">
|
||||
<meta property="og:url" content="{html.escape(url)}">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:title" content="{html.escape(oembed['title'])}">
|
||||
<meta name="twitter:title" content="{html.escape(oembed['title'])}">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:image" content="{html.escape(oembed['url'])}">
|
||||
<meta property="og:image:url" content="{html.escape(oembed['url'])}">
|
||||
<meta property="og:image:width" content="{oembed['width']}">
|
||||
<meta property="og:image:height" content="{oembed['height']}">
|
||||
<meta property="article:author" content="{html.escape(oembed['author_name'] or '')}">
|
||||
<link rel="alternate" type="application/json+oembed" href="{config.config["site_url"]}/api/oembed?url={quote(html.escape(url))}" title="{html.escape(config.config["name"])}"></head>
|
||||
''').replace("<html>", '<html prefix="og: http://ogp.me/ns#">').replace("<title>Loading...</title>", f"<title>{html.escape(oembed['title'])}</title>")
|
||||
return {"return_type": "custom", "content": new_html}
|
@ -74,8 +74,7 @@ def application(
|
||||
) -> Tuple[bytes]:
|
||||
try:
|
||||
ctx = _create_context(env)
|
||||
accept_header = ctx.get_header("Accept")
|
||||
if "*/*" not in accept_header and "application/json" not in accept_header:
|
||||
if "application/json" not in ctx.get_header("Accept"):
|
||||
raise errors.HttpNotAcceptable(
|
||||
"ValidationError", "This API only supports JSON responses."
|
||||
)
|
||||
@ -112,10 +111,6 @@ def application(
|
||||
finally:
|
||||
db.session.remove()
|
||||
|
||||
if type(response) == dict and response.get("return_type") == "custom":
|
||||
start_response(response.get("status_code", "200"), [("content-type", "text/html")])
|
||||
return (response.get("content", "").encode("utf-8"),)
|
||||
|
||||
start_response("200", [("content-type", "application/json")])
|
||||
return (_dump_json(response).encode("utf-8"),)
|
||||
|
||||
|
Reference in New Issue
Block a user