1
0
mirror of https://github.com/vichan-devel/vichan.git synced 2024-11-12 01:50:48 +01:00

Merge pull request #721 from Zankaria/dockerize

Dockerize Vichan
This commit is contained in:
Lorenzo Yario 2024-04-19 01:04:16 -07:00 committed by GitHub
commit 19fa804116
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 448 additions and 17 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
**/.git
**/.gitignore
/local-instances
**/.gitkeep

1
.gitignore vendored
View File

@ -44,5 +44,6 @@ Thumbs.db
#vichan custom #vichan custom
favicon.ico favicon.ico
/static/spoiler.png /static/spoiler.png
/local-instances
/vendor/ /vendor/

View File

@ -119,6 +119,11 @@ WebM support
------------ ------------
Read `inc/lib/webm/README.md` for information about enabling webm. Read `inc/lib/webm/README.md` for information about enabling webm.
Docker
------------
Vichan comes with a Dockerfile and docker-compose configuration, the latter aimed primarily at development and testing.
See the `docker/doc.md` file for more information.
vichan API vichan API
---------- ----------
vichan provides by default a 4chan-compatible JSON API. For documentation on this, see: vichan provides by default a 4chan-compatible JSON API. For documentation on this, see:

40
docker-compose.yml Normal file
View File

@ -0,0 +1,40 @@
services:
#nginx webserver + php 8.x
web:
build:
context: .
dockerfile: ./docker/nginx/Dockerfile
ports:
- "9090:80"
depends_on:
- db
volumes:
- ./local-instances/1/www:/var/www/html
- ./docker/nginx/vichan.conf:/etc/nginx/conf.d/default.conf
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/proxy.conf:/etc/nginx/conf.d/proxy.conf
links:
- php
php:
build:
context: .
dockerfile: ./docker/php/Dockerfile
volumes:
- ./local-instances/1/www:/var/www
- ./docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf
- ./docker/php/jit.ini:/usr/local/etc/php/conf.d/jit.ini
#MySQL Service
db:
image: mysql:8.0.35
container_name: db
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: vichan
MYSQL_ROOT_PASSWORD: password
volumes:
- ./local-instances/1/mysql:/var/lib/mysql

16
docker/doc.md Normal file
View File

@ -0,0 +1,16 @@
The `php-fpm` process runs containerized.
The php application always uses `/var/www` as it's work directory and home folder, and if `/var/www` is bind mounted it
is necessary to adjust the path passed via FastCGI to `php-fpm` by changing the root directory to `/var/www`.
This can achieved in nginx by setting the `fastcgi_param SCRIPT_FILENAME` to `/var/www/$fastcgi_script_name;`
The default docker compose settings are intended for development and testing purposes.
The folder structure expected by compose is as follows
```
<vichan-project>
└── local-instances
└── 1
├── mysql
└── www
```
The vichan container is by itself much less rigid.

8
docker/nginx/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM nginx:1.25.3-alpine
COPY . /code
RUN adduser --system www-data \
&& adduser www-data www-data
CMD [ "nginx", "-g", "daemon off;" ]
EXPOSE 80

34
docker/nginx/nginx.conf Normal file
View File

@ -0,0 +1,34 @@
# This and proxy.conf are based on
# https://github.com/dead-guru/devichan/blob/master/nginx/nginx.conf
user www-data;
worker_processes auto;
error_log /dev/stdout warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Switch logging to console out to view via Docker
access_log /dev/stdout;
error_log /dev/stdout warn;
sendfile on;
keepalive_timeout 5;
gzip on;
gzip_http_version 1.0;
gzip_vary on;
gzip_comp_level 6;
gzip_types text/xml text/plain text/css application/xhtml+xml application/xml application/rss+xml application/atom_xml application/x-javascript application/x-httpd-php;
gzip_disable "MSIE [1-6]\.";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-available/*.conf;
}

40
docker/nginx/proxy.conf Normal file
View File

@ -0,0 +1,40 @@
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=czone:4m max_size=50m inactive=120m;
proxy_temp_path /var/tmp/nginx;
proxy_cache_key "$scheme://$host$request_uri";
map $http_forwarded_request_id $x_request_id {
"" $request_id;
default $http_forwarded_request_id;
}
map $http_forwarded_forwarded_host $forwardedhost {
"" $host;
default $http_forwarded_forwarded_host;
}
map $http_x_forwarded_proto $fcgi_https {
default "";
https on;
}
map $http_x_forwarded_proto $real_scheme {
default $scheme;
https https;
}
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
real_ip_header X-Forwarded-For;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 172.18.0.0;
set_real_ip_from 192.168.0.0/24;
set_real_ip_from 127.0.0.0/8;
real_ip_recursive on;

66
docker/nginx/vichan.conf Normal file
View File

@ -0,0 +1,66 @@
upstream php-upstream {
server php:9000;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name vichan;
root /var/www/html;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.html index.php;
charset utf-8;
location ~ ^([^.\?]*[^\/])$ {
try_files $uri @addslash;
}
# Expire rules for static content
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|webp|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
log_not_found off;
add_header Cache-Control "public";
}
# CSS and Javascript
location ~* \.(?:css|js)$ {
expires 1y;
access_log off;
log_not_found off;
add_header Cache-Control "public";
}
location ~* \.(html)$ {
expires -1;
}
location @addslash {
return 301 $uri/;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
client_max_body_size 2G;
location ~ \.php$ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Request-Id $x_request_id;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Forwarded-Request-Id $x_request_id;
fastcgi_pass php-upstream;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/$fastcgi_script_name;
fastcgi_read_timeout 600;
include fastcgi_params;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
}

87
docker/php/Dockerfile Normal file
View File

@ -0,0 +1,87 @@
# Based on https://github.com/dead-guru/devichan/blob/master/php-fpm/Dockerfile
FROM composer AS composer
FROM php:8.1-fpm-alpine
RUN apk add --no-cache \
zlib \
zlib-dev \
libpng \
libpng-dev \
libjpeg-turbo \
libjpeg-turbo-dev \
libwebp \
libwebp-dev \
libcurl \
curl-dev \
imagemagick \
graphicsmagick \
gifsicle \
ffmpeg \
bind-tools \
gettext \
gettext-dev \
icu-dev \
oniguruma \
oniguruma-dev \
libmcrypt \
libmcrypt-dev \
lz4-libs \
lz4-dev \
imagemagick-dev \
pcre-dev \
$PHPIZE_DEPS \
&& docker-php-ext-configure gd \
--with-webp=/usr/include/webp \
--with-jpeg=/usr/include \
&& docker-php-ext-install -j$(nproc) \
gd \
curl \
bcmath \
opcache \
pdo_mysql \
gettext \
intl \
mbstring \
&& pecl update-channels \
&& pecl install -o -f igbinary \
&& pecl install redis \
&& pecl install imagick \
$$ docker-php-ext-enable \
igbinary \
redis \
imagick \
&& apk del \
zlib-dev \
libpng-dev \
libjpeg-turbo-dev \
libwebp-dev \
curl-dev \
gettext-dev \
oniguruma-dev \
libmcrypt-dev \
lz4-dev \
imagemagick-dev \
pcre-dev \
$PHPIZE_DEPS \
&& rm -rf /var/cache/*
RUN rmdir /var/www/html \
&& install -d -m 744 -o www-data -g www-data /var/www \
&& install -d -m 700 -o www-data -g www-data /var/tmp/vichan \
&& install -d -m 700 -o www-data -g www-data /var/cache/gen-cache \
&& install -d -m 700 -o www-data -g www-data /var/cache/template-cache
# Copy the bootstrap script.
COPY ./docker/php/bootstrap.sh /usr/local/bin/bootstrap.sh
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
# Copy the actual project (use .dockerignore to exclude stuff).
COPY . /code
# Install the compose depedencies.
RUN cd /code && composer install
WORKDIR "/var/www"
CMD [ "bootstrap.sh" ]
EXPOSE 9000

View File

@ -0,0 +1,16 @@
# syntax = devthefuture/dockerfile-x
INCLUDE ./docker/php/Dockerfile
RUN apk add --no-cache \
linux-headers \
$PHPIZE_DEPS \
&& pecl update-channels \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug \
&& apk del \
linux-headers \
$PHPIZE_DEPS \
&& rm -rf /var/cache/*
ENV XDEBUG_OUT_DIR=/var/www/xdebug_out
CMD [ "bootstrap.sh" ]

87
docker/php/bootstrap.sh Executable file
View File

@ -0,0 +1,87 @@
#!/bin/sh
set -eu
function set_cfg() {
if [ -L "/var/www/inc/$1" ]; then
echo "INFO: Resetting $1"
rm "/var/www/inc/$1"
cp "/code/inc/$1" "/var/www/inc/$1"
chown www-data "/var/www/inc/$1"
chgrp www-data "/var/www/inc/$1"
chmod 600 "/var/www/inc/$1"
else
echo "INFO: Using existing $1"
fi
}
if ! mountpoint -q /var/www; then
echo "WARNING: '/var/www' is not a mountpoint. All the data will remain inside the container!"
fi
if [ ! -w /var/www ] ; then
echo "ERROR: '/var/www' is not writable. Closing."
exit 1
fi
if [ -z "${XDEBUG_OUT_DIR:-''}" ] ; then
echo "INFO: Initializing xdebug out directory at $XDEBUG_OUT_DIR"
mkdir -p "$XDEBUG_OUT_DIR"
chown www-data "$XDEBUG_OUT_DIR"
chgrp www-data "$XDEBUG_OUT_DIR"
chmod 755 "$XDEBUG_OUT_DIR"
fi
# Link the entrypoints from the exposed directory.
ln -nfs \
/code/tools/ \
/code/*.php \
/code/LICENSE.* \
/code/install.sql \
/var/www/
# Static files accessible from the webserver must be copied.
cp -ur /code/static /var/www/
cp -ur /code/stylesheets /var/www/
# Ensure correct permissions are set, since this might be bind mount.
chown www-data /var/www
chgrp www-data /var/www
# Initialize an empty robots.txt with the default if it doesn't exist.
touch /var/www/robots.txt
# Link the cache and tmp files directory.
ln -nfs /var/tmp/vichan /var/www/tmp
# Link the javascript directory.
ln -nfs /code/js /var/www/
# Link the html templates directory and it's cache.
ln -nfs /code/templates /var/www/
ln -nfs -T /var/cache/template-cache /var/www/templates/cache
chown -h www-data /var/www/templates/cache
chgrp -h www-data /var/www/templates/cache
# Link the generic cache.
ln -nfs -T /var/cache/gen-cache /var/www/tmp/cache
chown -h www-data /var/www/tmp/cache
chgrp -h www-data /var/www/tmp/cache
# Create the included files directory and link them
install -d -m 700 -o www-data -g www-data /var/www/inc
for file in /code/inc/*; do
file="${file##*/}"
if [ ! -e /var/www/inc/$file ]; then
ln -s /code/inc/$file /var/www/inc/
fi
done
# Copy an empty instance configuration if the file is a link (it was linked because it did not exist before).
set_cfg 'instance-config.php'
set_cfg 'secrets.php'
# Link the composer dependencies.
ln -nfs /code/vendor /var/www/
# Start the php-fpm server.
exec php-fpm

2
docker/php/jit.ini Normal file
View File

@ -0,0 +1,2 @@
opcache.jit_buffer_size=192M
opcache.jit=tracing

13
docker/php/www.conf Normal file
View File

@ -0,0 +1,13 @@
[www]
access.log = /proc/self/fd/2
; Ensure worker stdout and stderr are sent to the main error log.
catch_workers_output = yes
decorate_workers_output = no
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = static
pm.max_children = 16

View File

@ -0,0 +1,7 @@
zend_extension=xdebug
[xdebug]
xdebug.mode = profile
xdebug.start_with_request = start
error_reporting = E_ALL
xdebug.output_dir = /var/www/xdebug_out

View File

@ -11,12 +11,14 @@ $twig = false;
function load_twig() { function load_twig() {
global $twig, $config; global $twig, $config;
$cache_dir = "{$config['dir']['template']}/cache/";
$loader = new Twig\Loader\FilesystemLoader($config['dir']['template']); $loader = new Twig\Loader\FilesystemLoader($config['dir']['template']);
$loader->setPaths($config['dir']['template']); $loader->setPaths($config['dir']['template']);
$twig = new Twig\Environment($loader, array( $twig = new Twig\Environment($loader, array(
'autoescape' => false, 'autoescape' => false,
'cache' => is_writable('templates') || (is_dir('templates/cache') && is_writable('templates/cache')) ? 'cache' => is_writable('templates/') || (is_dir($cache_dir) && is_writable($cache_dir)) ?
new Twig_Cache_TinyboardFilesystem("{$config['dir']['template']}/cache") : false, new Twig_Cache_TinyboardFilesystem($cache_dir) : false,
'debug' => $config['debug'], 'debug' => $config['debug'],
'auto_reload' => $config['twig_auto_reload'] 'auto_reload' => $config['twig_auto_reload']
)); ));
@ -28,17 +30,17 @@ function load_twig() {
function Element($templateFile, array $options) { function Element($templateFile, array $options) {
global $config, $debug, $twig, $build_pages; global $config, $debug, $twig, $build_pages;
if (!$twig) if (!$twig)
load_twig(); load_twig();
if (function_exists('create_pm_header') && ((isset($options['mod']) && $options['mod']) || isset($options['__mod'])) && !preg_match('!^mod/!', $templateFile)) { if (function_exists('create_pm_header') && ((isset($options['mod']) && $options['mod']) || isset($options['__mod'])) && !preg_match('!^mod/!', $templateFile)) {
$options['pm'] = create_pm_header(); $options['pm'] = create_pm_header();
} }
if (isset($options['body']) && $config['debug']) { if (isset($options['body']) && $config['debug']) {
$_debug = $debug; $_debug = $debug;
if (isset($debug['start'])) { if (isset($debug['start'])) {
$_debug['time']['total'] = '~' . round((microtime(true) - $_debug['start']) * 1000, 2) . 'ms'; $_debug['time']['total'] = '~' . round((microtime(true) - $_debug['start']) * 1000, 2) . 'ms';
$_debug['time']['init'] = '~' . round(($_debug['start_debug'] - $_debug['start']) * 1000, 2) . 'ms'; $_debug['time']['init'] = '~' . round(($_debug['start_debug'] - $_debug['start']) * 1000, 2) . 'ms';
@ -56,15 +58,15 @@ function Element($templateFile, array $options) {
str_replace("\n", '<br/>', utf8tohtml(print_r($_debug, true))) . str_replace("\n", '<br/>', utf8tohtml(print_r($_debug, true))) .
'</pre>'; '</pre>';
} }
// Read the template file // Read the template file
if (@file_get_contents("{$config['dir']['template']}/${templateFile}")) { if (@file_get_contents("{$config['dir']['template']}/${templateFile}")) {
$body = $twig->render($templateFile, $options); $body = $twig->render($templateFile, $options);
if ($config['minify_html'] && preg_match('/\.html$/', $templateFile)) { if ($config['minify_html'] && preg_match('/\.html$/', $templateFile)) {
$body = trim(preg_replace("/[\t\r\n]/", '', $body)); $body = trim(preg_replace("/[\t\r\n]/", '', $body));
} }
return $body; return $body;
} else { } else {
throw new Exception("Template file '${templateFile}' does not exist or is empty in '{$config['dir']['template']}'!"); throw new Exception("Template file '${templateFile}' does not exist or is empty in '{$config['dir']['template']}'!");
@ -102,7 +104,7 @@ class Tinyboard extends Twig\Extension\AbstractExtension
new Twig\TwigFilter('cloak_mask', 'cloak_mask'), new Twig\TwigFilter('cloak_mask', 'cloak_mask'),
); );
} }
/** /**
* Returns a list of functions to add to the existing list. * Returns a list of functions to add to the existing list.
* *
@ -122,7 +124,7 @@ class Tinyboard extends Twig\Extension\AbstractExtension
new Twig\TwigFunction('link_for', 'link_for') new Twig\TwigFunction('link_for', 'link_for')
); );
} }
/** /**
* Returns the name of the extension. * Returns the name of the extension.
* *
@ -154,7 +156,7 @@ function twig_hasPermission_filter($mod, $permission, $board = null) {
function twig_extension_filter($value, $case_insensitive = true) { function twig_extension_filter($value, $case_insensitive = true) {
$ext = mb_substr($value, mb_strrpos($value, '.') + 1); $ext = mb_substr($value, mb_strrpos($value, '.') + 1);
if($case_insensitive) if($case_insensitive)
$ext = mb_strtolower($ext); $ext = mb_strtolower($ext);
return $ext; return $ext;
} }
@ -179,7 +181,7 @@ function twig_filename_truncate_filter($value, $length = 30, $separator = '…')
$value = strrev($value); $value = strrev($value);
$array = array_reverse(explode(".", $value, 2)); $array = array_reverse(explode(".", $value, 2));
$array = array_map("strrev", $array); $array = array_map("strrev", $array);
$filename = &$array[0]; $filename = &$array[0];
$extension = isset($array[1]) ? $array[1] : false; $extension = isset($array[1]) ? $array[1] : false;

View File

@ -5,6 +5,11 @@ define('VERSION', '5.1.4');
require 'inc/bootstrap.php'; require 'inc/bootstrap.php';
loadConfig(); loadConfig();
if (!is_writable('inc/secrets.php') || !is_writable('inc/')) {
echo 'install.php does not have permission to write to /inc/secrets.php and/or /inc/, without permission the installer cannot continue';
exit();
}
// Salt generators // Salt generators
class SaltGen { class SaltGen {
public $salt_length = 128; public $salt_length = 128;
@ -856,14 +861,14 @@ if ($step == 0) {
array( array(
'category' => 'File permissions', 'category' => 'File permissions',
'name' => getcwd() . '/templates/cache', 'name' => getcwd() . '/templates/cache',
'result' => is_writable('templates') || (is_dir('templates/cache') && is_writable('templates/cache')), 'result' => is_dir('templates/cache/') && is_writable('templates/cache/'),
'required' => true, 'required' => true,
'message' => 'You must give vichan permission to create (and write to) the <code>templates/cache</code> directory or performance will be drastically reduced.' 'message' => 'You must give vichan permission to create (and write to) the <code>templates/cache</code> directory or performance will be drastically reduced.'
), ),
array( array(
'category' => 'File permissions', 'category' => 'File permissions',
'name' => getcwd() . '/tmp/cache', 'name' => getcwd() . '/tmp/cache',
'result' => is_dir('tmp/cache') && is_writable('tmp/cache'), 'result' => is_dir('tmp/cache/') && is_writable('tmp/cache/'),
'required' => true, 'required' => true,
'message' => 'You must give vichan permission to write to the <code>tmp/cache</code> directory.' 'message' => 'You must give vichan permission to write to the <code>tmp/cache</code> directory.'
), ),
@ -1036,4 +1041,3 @@ if ($step == 0) {
echo Element('page.html', $page); echo Element('page.html', $page);
} }

View File

@ -1 +0,0 @@