mirror of
https://github.com/gfacciol/zotero_dataserver-docker.git
synced 2021-05-12 18:32:26 +03:00
initial commit
This commit is contained in:
107
Dockerfile
Normal file
107
Dockerfile
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
FROM debian:wheezy-backports
|
||||||
|
MAINTAINER Gabriele Facciolo <gfacciol@gmail.com>
|
||||||
|
# Following http://git.27o.de/dataserver/about/Installation-Instructions-for-Debian-Wheezy.md
|
||||||
|
|
||||||
|
# debian packages
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
apache2 libapache2-mod-php5 mysql-server memcached zendframework php5-cli php5-memcached php5-mysql php5-curl \
|
||||||
|
apache2 uwsgi uwsgi-plugin-psgi libplack-perl libdigest-hmac-perl libjson-xs-perl libfile-util-perl libapache2-mod-uwsgi libswitch-perl \
|
||||||
|
git gnutls-bin runit wget curl net-tools vim build-essential
|
||||||
|
|
||||||
|
# Zotero
|
||||||
|
RUN mkdir -p /srv/zotero/log/upload && \
|
||||||
|
mkdir -p /srv/zotero/log/download && \
|
||||||
|
mkdir -p /srv/zotero/log/error && \
|
||||||
|
mkdir -p /srv/zotero/log/api-errors && \
|
||||||
|
mkdir -p /srv/zotero/log/sync-errors && \
|
||||||
|
mkdir -p /srv/zotero/dataserver && \
|
||||||
|
mkdir -p /srv/zotero/zss && \
|
||||||
|
mkdir -p /var/log/httpd/sync-errors && \
|
||||||
|
mkdir -p /var/log/httpd/api-errors && \
|
||||||
|
chown www-data: /var/log/httpd/sync-errors && \
|
||||||
|
chown www-data: /var/log/httpd/api-errors
|
||||||
|
|
||||||
|
# Dataserver
|
||||||
|
RUN git clone --depth=1 git://git.27o.de/dataserver /srv/zotero/dataserver && \
|
||||||
|
chown www-data:www-data /srv/zotero/dataserver/tmp
|
||||||
|
#RUN cd /srv/zotero/dataserver/include && rm -r Zend && ln -s /usr/share/php/libzend-framework-php/Zend
|
||||||
|
RUN cd /srv/zotero/dataserver/include && rm -r Zend && ln -s /usr/share/php/Zend
|
||||||
|
|
||||||
|
#Apache2
|
||||||
|
#certtool -p --sec-param high --outfile /etc/apache2/zotero.key
|
||||||
|
#certtool -s --load-privkey /etc/apache2/zotero.key --outfile /etc/apache2/zotero.cert
|
||||||
|
ADD apache/zotero.key /etc/apache2/
|
||||||
|
ADD apache/zotero.cert /etc/apache2/
|
||||||
|
ADD apache/sites-zotero.conf /etc/apache2/sites-available/zotero
|
||||||
|
ADD apache/dot.htaccess /srv/zotero/dataserver/htdocs/\.htaccess
|
||||||
|
RUN a2enmod ssl && \
|
||||||
|
a2enmod rewrite && \
|
||||||
|
a2ensite zotero
|
||||||
|
|
||||||
|
#Mysql
|
||||||
|
ADD mysql/zotero.cnf /etc/mysql/conf.d/zotero.cnf
|
||||||
|
ADD mysql/setup_db /srv/zotero/dataserver/misc/setup_db
|
||||||
|
RUN /etc/init.d/mysql start && \
|
||||||
|
mysqladmin -u root password password && \
|
||||||
|
cd /srv/zotero/dataserver/misc/ && \
|
||||||
|
./setup_db
|
||||||
|
|
||||||
|
|
||||||
|
# Zotero Configuration
|
||||||
|
ADD dataserver/dbconnect.inc.php dataserver/config.inc.php /srv/zotero/dataserver/include/config/
|
||||||
|
ADD dataserver/sv/zotero-download /etc/sv/zotero-download
|
||||||
|
ADD dataserver/sv/zotero-upload /etc/sv/zotero-upload
|
||||||
|
ADD dataserver/sv/zotero-error /etc/sv/zotero-error
|
||||||
|
RUN cd /etc/service && \
|
||||||
|
ln -s ../sv/zotero-download /etc/service/ && \
|
||||||
|
ln -s ../sv/zotero-upload /etc/service/ && \
|
||||||
|
ln -s ../sv/zotero-error /etc/service/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ZSS
|
||||||
|
RUN git clone --depth=1 git://git.27o.de/zss /srv/zotero/zss && \
|
||||||
|
mkdir /srv/zotero/storage && \
|
||||||
|
chown www-data:www-data /srv/zotero/storage
|
||||||
|
|
||||||
|
ADD zss/zss.yaml /etc/uwsgi/apps-available/
|
||||||
|
ADD zss/ZSS.pm /srv/zotero/zss/
|
||||||
|
ADD zss/zss.psgi /srv/zotero/zss/
|
||||||
|
RUN ln -s /etc/uwsgi/apps-available/zss.yaml /etc/uwsgi/apps-enabled
|
||||||
|
# fix uwsgi init scipt (always fails)
|
||||||
|
ADD patches/uwsgi /etc/init.d/uwsgi
|
||||||
|
|
||||||
|
|
||||||
|
## failed attempt to install Zotero Web-Library locally
|
||||||
|
## not working
|
||||||
|
#RUN cd /srv/ && \
|
||||||
|
# git clone --depth=1 --recursive https://github.com/zotero/web-library.git && \
|
||||||
|
# curl -sL https://deb.nodesource.com/setup_4.x | bash - && apt-get install -y nodejs && \
|
||||||
|
# cd /srv/web-library && \
|
||||||
|
# npm install && \
|
||||||
|
# npm install prompt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# replace custom /srv/zotero/dataserver/admin/add_user that allows to write the password
|
||||||
|
ADD patches/add_user /srv/zotero/dataserver/admin/add_user
|
||||||
|
|
||||||
|
# TEST ADD USER: test PASSWORD: test
|
||||||
|
RUN service mysql start && service memcached start && \
|
||||||
|
cd /srv/zotero/dataserver/admin && \
|
||||||
|
./add_user 101 test test && \
|
||||||
|
./add_user 102 test2 test2 && \
|
||||||
|
./add_group -o test -f members -r members -e members testgroup && \
|
||||||
|
./add_groupuser testgroup test2 member
|
||||||
|
|
||||||
|
|
||||||
|
# docker server startup
|
||||||
|
EXPOSE 80 443
|
||||||
|
|
||||||
|
CMD service mysql start && \
|
||||||
|
service uwsgi start && \
|
||||||
|
service apache2 start && \
|
||||||
|
service memcached start && \
|
||||||
|
bash -c "/usr/sbin/runsvdir-start&" && \
|
||||||
|
/bin/bash
|
||||||
47
README.md
47
README.md
@@ -1 +1,46 @@
|
|||||||
# Docker image for Zotero Data Server
|
# Docker image for a Zotero Data Server
|
||||||
|
|
||||||
|
This image was build following the instructions for installing a Zotero dataserver at (http://git.27o.de/dataserver/about/), which is an updated procedure of [this document](https://github.com/Panzerkampfwagen/dataserver/blob/master/misc/Zotero_Data_Server_Installation_Debian.pdf).
|
||||||
|
|
||||||
|
|
||||||
|
## Build the image
|
||||||
|
|
||||||
|
docker build -t zotero .
|
||||||
|
|
||||||
|
The resulting image is configured run a dataserver on https://localhost/.
|
||||||
|
|
||||||
|
To customize the installation the following files must be edited:
|
||||||
|
* SSL certificate: apache/zotero.{cert,key}. The current certificate is self-signed for localhost.
|
||||||
|
* Apache site config: apache/sites-zotero.conf.
|
||||||
|
* Dataserver: dataserver/config.inc.php. To match the site config.
|
||||||
|
* MySQL credentials/passwords: mysql/setup\_db and dataserver/dbconnect.inc.php accordingly.
|
||||||
|
|
||||||
|
The build procedure also creates a couple of test users: test:test and test2:test2.
|
||||||
|
|
||||||
|
|
||||||
|
## Start the dataserver
|
||||||
|
|
||||||
|
docker run -p 80:80 -p 443:443 -t -i zotero
|
||||||
|
|
||||||
|
This will start the dataserver on https://localhost/
|
||||||
|
|
||||||
|
https://localhost/sync/login?version=9&username=test&password=test
|
||||||
|
|
||||||
|
because of the self-signed certificate some browsers may refuse to connect the server.
|
||||||
|
|
||||||
|
|
||||||
|
## Patch the standalone client to use the new dataserver
|
||||||
|
|
||||||
|
We follow the procedure of (http://git.27o.de/dataserver/about/Zotero-Client.md).
|
||||||
|
Download the Zotero client, and change these two lines in resource/config.js inside the zotero.jar archive (zip)
|
||||||
|
|
||||||
|
SYNC_URL: 'https://localhost/sync/',
|
||||||
|
API_URL: 'https://localhost/',
|
||||||
|
|
||||||
|
If the server uses a self-signed certificate an exception should be added to the client. A cert\_override.txt file must added to the local profile generated by zotero client:
|
||||||
|
|
||||||
|
~/Library/Application\ Support/Zotero/Profiles/[something].default/ MAC
|
||||||
|
~/.zotero/Profiles/[something].default/ Linux
|
||||||
|
c:Users/<username>/AppData/Roaming/Zotero/Zotero/ Win
|
||||||
|
|
||||||
|
The cert\_override.txt file can be generated with Firefox following (https://groups.google.com/d/msg/zotero-dev/MEwLaptJIzI/PVDAFJiqEgAJ)
|
||||||
|
|||||||
30
apache/dot.htaccess
Normal file
30
apache/dot.htaccess
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# If on a testing site, deny by default unless IP is allowed
|
||||||
|
SetEnvIf Host "apidev" ACCESS_CONTROL
|
||||||
|
SetEnvIf Host "syncdev" ACCESS_CONTROL
|
||||||
|
####### Local
|
||||||
|
SetEnvIf X-Forwarded-For "192.168.1.|" !ACCESS_CONTROL
|
||||||
|
order deny,allow
|
||||||
|
deny from env=ACCESS_CONTROL
|
||||||
|
|
||||||
|
#php_flag zlib.output_compression On
|
||||||
|
#php_value zlib.output_compression_level 5
|
||||||
|
php_value short_open_tag 1
|
||||||
|
|
||||||
|
php_value include_path "../include"
|
||||||
|
php_value auto_prepend_file "header.inc.php"
|
||||||
|
php_value auto_append_file "footer.inc.php"
|
||||||
|
|
||||||
|
php_value memory_limit 500M
|
||||||
|
|
||||||
|
#php_value xdebug.show_local_vars 1
|
||||||
|
#php_value xdebug.profiler_enable 1
|
||||||
|
#php_value xdebug.profiler_enable_trigger 1
|
||||||
|
#php_value xdebug.profiler_output_dir /tmp/xdebug
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# If file or directory doesn't exist, pass to director for MVC redirections
|
||||||
|
RewriteCond %{SCRIPT_FILENAME} !-f
|
||||||
|
RewriteCond %{SCRIPT_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_URI} !^/zotero
|
||||||
|
RewriteRule .* index.php [L]
|
||||||
46
apache/sites-zotero.conf
Normal file
46
apache/sites-zotero.conf
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<VirtualHost *:80>
|
||||||
|
DocumentRoot /srv/web-library
|
||||||
|
<Directory />
|
||||||
|
Options FollowSymLinks
|
||||||
|
AllowOverride None
|
||||||
|
</Directory>
|
||||||
|
<Directory /srv/web-library/>
|
||||||
|
Options Indexes FollowSymLinks MultiViews
|
||||||
|
AllowOverride None
|
||||||
|
Order allow,deny
|
||||||
|
allow from all
|
||||||
|
</Directory>
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
|
||||||
|
<VirtualHost *:443>
|
||||||
|
DocumentRoot /srv/zotero/dataserver/htdocs
|
||||||
|
SSLEngine on
|
||||||
|
SSLCertificateFile /etc/apache2/zotero.cert
|
||||||
|
SSLCertificateKeyFile /etc/apache2/zotero.key
|
||||||
|
|
||||||
|
<Location /zotero/>
|
||||||
|
SetHandler uwsgi-handler
|
||||||
|
uWSGISocket /var/run/uwsgi/app/zss/socket
|
||||||
|
uWSGImodifier1 5
|
||||||
|
</Location>
|
||||||
|
|
||||||
|
<Directory "/srv/zotero/dataserver/htdocs/">
|
||||||
|
Options FollowSymLinks MultiViews
|
||||||
|
AllowOverride All
|
||||||
|
|
||||||
|
#2.2
|
||||||
|
Order allow,deny
|
||||||
|
Allow from all
|
||||||
|
# 2.4
|
||||||
|
# Require all granted
|
||||||
|
#
|
||||||
|
# If you are using a more recent version of apache
|
||||||
|
# and are getting 403 errors, replace the Order and
|
||||||
|
# Allow lines with:
|
||||||
|
# Require all granted
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
ErrorLog /srv/zotero/log/error.log
|
||||||
|
CustomLog /srv/zotero/log/access.log common
|
||||||
|
</VirtualHost>
|
||||||
24
apache/zotero.cert
Normal file
24
apache/zotero.cert
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEFTCCAmegAwIBAgIEV7XanjANBgkqhkiG9w0BAQsFADAAMB4XDTE2MDgxODE1
|
||||||
|
NTYxNloXDTQ0MDEwNDE1NTYxOFowADCCAbgwDQYJKoZIhvcNAQEBBQADggGlADCC
|
||||||
|
AaACggGXANJ8/OlJsFwD0I2Xgjas22dg5ESYcj+xf5IBNd1FVQxKUfpLA/vpEV9n
|
||||||
|
bIyDHZgXdiKKQYdfTUbdJVX7ilSmmvDJ9wPjfa/72L7fCJl2oCY98pNVNBelXe/u
|
||||||
|
zABW4PZVGNoaLE/H5/Bar14E9l0YJ6DbaaEQ8xNeieTZkGZ0SUwVdC0p6V11dEHG
|
||||||
|
MPhEw3aXH0kAy9KAAPmkXVnmSKsRnCaVft1ob8IMvYPmHwlYa4uYbW3JhOu8NkMJ
|
||||||
|
kVWJ3JEdWfWPX/M7VT7DFyC42JP17NHJgViFS8tuYGiWtwFDeY1grExB3ZDFySGh
|
||||||
|
s3NYSMRV710xAY5M7oqKnojv53tGzeQOQ5Mhv1A9i62f4h3xzwnQ7rroPprU5h4Y
|
||||||
|
LPvIQ4RnVOH1o7+Ug70SG8IOWPP8wX8egBWalWqtrP5XyADEUwZM3f0EWUUMnCg1
|
||||||
|
kZp1woiE4eRI6YdbWBrJlelcF6DSo+GONHHnyp438PS9eiLE0Xmv4ZyRXx+TjIMC
|
||||||
|
A1RqydW6qsHmvaBuAwCKggoFvQ5vSpAolgBFIOcCAwEAAaNrMGkwDAYDVR0TAQH/
|
||||||
|
BAIwADAlBgNVHREEHjAcgglsb2NhbGhvc3SCCWxvY2FsaG9zdIcEfwAAATATBgNV
|
||||||
|
HSUEDDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUTnN76df4/7g8WmMd7J2UTIUO/wcw
|
||||||
|
DQYJKoZIhvcNAQELBQADggGXAM1Gdi5xk74RPJTIwdRn/J7YLQMwwUB0Nhsk/FvO
|
||||||
|
9pjQJrXBNq7dPHaLPwpFCK6YjDiVm3rV7f96mdSpo7A9GtH0d2Cx6I1a+3h/zHUu
|
||||||
|
XSWLoLPQpt84Qk2qhNRNNQhBc6NbpSS/754wQH4o+QiVbfrti2f0SaGAlso1tJbB
|
||||||
|
/gckYA9SjCDlBPL4nUjms2w32ooiXxUkYcgAp0a9TBpQ6YgNL5bD3m0I2kPi/VZD
|
||||||
|
UIaMoj6lw5xUKaT8EhowrwO5796bCBB3sCGN0YciUrMrl04ZWYqbNuZYtFCb2kWt
|
||||||
|
K9wkiIoZzn+CMEfDFmFODG4qf0YvwO+qZz26ph4gJJ6XIi8vcZJam9HBWBvb/TOZ
|
||||||
|
UZVZKwHvTPkVFLyCd8q8S3LPlSrvWy4m63jtp6FGbOt0lQyVEt/L/xsig72UXBxl
|
||||||
|
3QfdPEHbr5pL8L8HNHElT2SB8q2OKFpHSdQtwA45jCA7W3OHtCIYvIhf34fAIHmX
|
||||||
|
2rglG/EHHGCZSPaC6lzbAyXvcuGyQ5ENUQiEL84nNm1vo51p6zlzR/E=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
41
apache/zotero.key
Normal file
41
apache/zotero.key
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
3
cert_override.txt
Normal file
3
cert_override.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# PSM Certificate Override Settings file
|
||||||
|
# This is a generated file! Do not edit.
|
||||||
|
localhost:443 OID.2.16.840.1.101.3.4.2.1 A1:B0:AF:69:BC:F0:59:39:3A:BF:2C:8C:80:05:6B:9F:5F:80:69:BB:12:8C:92:07:C7:B4:E9:2B:90:82:AF:E9 U AAAAAAAAAAAAAAAEAAAAAle12p4wAA==
|
||||||
85
dataserver/config.inc.php
Normal file
85
dataserver/config.inc.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?
|
||||||
|
class Z_CONFIG {
|
||||||
|
public static $API_ENABLED = true;
|
||||||
|
public static $SYNC_ENABLED = true;
|
||||||
|
public static $PROCESSORS_ENABLED = true;
|
||||||
|
public static $MAINTENANCE_MESSAGE = 'Server updates in progress. Please try again in a few minutes.';
|
||||||
|
|
||||||
|
public static $TESTING_SITE = false;
|
||||||
|
public static $DEV_SITE = false;
|
||||||
|
|
||||||
|
public static $DEBUG_LOG = false;
|
||||||
|
|
||||||
|
public static $BASE_URI = 'http://zotero.org/';
|
||||||
|
public static $API_BASE_URI = 'https://localhost/';
|
||||||
|
public static $WWW_BASE_URI = '';
|
||||||
|
public static $SYNC_DOMAIN = 'sync';
|
||||||
|
|
||||||
|
public static $AUTH_SALT = 'sometext';
|
||||||
|
public static $API_SUPER_USERNAME = 'someusername';
|
||||||
|
public static $API_SUPER_PASSWORD = 'somepassword';
|
||||||
|
|
||||||
|
public static $AWS_ACCESS_KEY = '';
|
||||||
|
public static $AWS_SECRET_KEY = 'yoursecretkey';
|
||||||
|
public static $S3_BUCKET = 'zotero';
|
||||||
|
public static $S3_ENDPOINT = 'localhost';
|
||||||
|
public static $S3_USE_SSL = true;
|
||||||
|
public static $S3_VALIDATE_SSL = false;
|
||||||
|
|
||||||
|
public static $URI_PREFIX_DOMAIN_MAP = array(
|
||||||
|
'/sync/' => 'sync'
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $MEMCACHED_ENABLED = true;
|
||||||
|
public static $MEMCACHED_SERVERS = array(
|
||||||
|
'localhost:11211'
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $TRANSLATION_SERVERS = array(
|
||||||
|
"translation1.localdomain:1969"
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $CITATION_SERVERS = array(
|
||||||
|
"citeserver1.localdomain:8080", "citeserver2.localdomain:8080"
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $ATTACHMENT_SERVER_HOSTS = array("files1.localdomain", "files2.localdomain");
|
||||||
|
public static $ATTACHMENT_SERVER_DYNAMIC_PORT = 80;
|
||||||
|
public static $ATTACHMENT_SERVER_STATIC_PORT = 81;
|
||||||
|
public static $ATTACHMENT_SERVER_URL = "https://files.example.net";
|
||||||
|
public static $ATTACHMENT_SERVER_DOCROOT = "/var/www/attachments/";
|
||||||
|
|
||||||
|
public static $STATSD_ENABLED = false;
|
||||||
|
public static $STATSD_PREFIX = "";
|
||||||
|
public static $STATSD_HOST = "monitor.localdomain";
|
||||||
|
public static $STATSD_PORT = 8125;
|
||||||
|
|
||||||
|
public static $LOG_TO_SCRIBE = false;
|
||||||
|
public static $LOG_ADDRESS = '';
|
||||||
|
public static $LOG_PORT = 1463;
|
||||||
|
public static $LOG_TIMEZONE = 'US/Eastern';
|
||||||
|
public static $LOG_TARGET_DEFAULT = 'errors';
|
||||||
|
|
||||||
|
public static $PROCESSOR_PORT_DOWNLOAD = 3455;
|
||||||
|
public static $PROCESSOR_PORT_UPLOAD = 3456;
|
||||||
|
public static $PROCESSOR_PORT_ERROR = 3457;
|
||||||
|
|
||||||
|
public static $PROCESSOR_LOG_TARGET_DOWNLOAD = 'sync-processor-download';
|
||||||
|
public static $PROCESSOR_LOG_TARGET_UPLOAD = 'sync-processor-upload';
|
||||||
|
public static $PROCESSOR_LOG_TARGET_ERROR = 'sync-processor-error';
|
||||||
|
|
||||||
|
public static $SYNC_DOWNLOAD_SMALLEST_FIRST = false;
|
||||||
|
public static $SYNC_UPLOAD_SMALLEST_FIRST = false;
|
||||||
|
|
||||||
|
// Set some things manually for running via command line
|
||||||
|
public static $CLI_PHP_PATH = '/usr/bin/php';
|
||||||
|
public static $CLI_DOCUMENT_ROOT = "/srv/zotero/dataserver/";
|
||||||
|
|
||||||
|
public static $SYNC_ERROR_PATH = '/srv/zotero/log/sync-errors/';
|
||||||
|
public static $API_ERROR_PATH = '/srv/zotero/log/api-errors/';
|
||||||
|
|
||||||
|
public static $CACHE_VERSION_ATOM_ENTRY = 1;
|
||||||
|
public static $CACHE_VERSION_BIB = 1;
|
||||||
|
public static $CACHE_VERSION_ITEM_DATA = 1;
|
||||||
|
}
|
||||||
|
?>
|
||||||
45
dataserver/dbconnect.inc.php
Normal file
45
dataserver/dbconnect.inc.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?
|
||||||
|
function Zotero_dbConnectAuth($db) {
|
||||||
|
$charset = '';
|
||||||
|
|
||||||
|
if ($db == 'master') {
|
||||||
|
$host = 'localhost';
|
||||||
|
$port = 3306;
|
||||||
|
$db = 'zotero_master';
|
||||||
|
$user = 'zotero';
|
||||||
|
$pass = 'foobar';
|
||||||
|
}
|
||||||
|
else if ($db == 'shard') {
|
||||||
|
$host = false;
|
||||||
|
$port = false;
|
||||||
|
$db = false;
|
||||||
|
$user = 'zotero';
|
||||||
|
$pass = 'foobar';
|
||||||
|
}
|
||||||
|
else if ($db == 'id1') {
|
||||||
|
$host = 'localhost';
|
||||||
|
$port = 3306;
|
||||||
|
$db = 'zotero_ids';
|
||||||
|
$user = 'zotero';
|
||||||
|
$pass = 'foobar';
|
||||||
|
}
|
||||||
|
else if ($db == 'id2') {
|
||||||
|
$host = 'localhost';
|
||||||
|
$port = 3306;
|
||||||
|
$db = 'zotero_ids';
|
||||||
|
$user = 'zotero';
|
||||||
|
$pass = 'foobar';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Exception("Invalid db '$db'");
|
||||||
|
}
|
||||||
|
return array(
|
||||||
|
'host'=>$host,
|
||||||
|
'port'=>$port,
|
||||||
|
'db'=>$db,
|
||||||
|
'user'=>$user,
|
||||||
|
'pass'=>$pass,
|
||||||
|
'charset'=>$charset
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
3
dataserver/sv/zotero-download/log/run
Executable file
3
dataserver/sv/zotero-download/log/run
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
exec svlogd /srv/zotero/log/download
|
||||||
5
dataserver/sv/zotero-download/run
Executable file
5
dataserver/sv/zotero-download/run
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
cd /srv/zotero/dataserver/processor/download
|
||||||
|
exec 2>&1
|
||||||
|
exec chpst -u www-data:www-data php5 daemon.php
|
||||||
3
dataserver/sv/zotero-error/log/run
Executable file
3
dataserver/sv/zotero-error/log/run
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
exec svlogd /srv/zotero/log/error
|
||||||
5
dataserver/sv/zotero-error/run
Executable file
5
dataserver/sv/zotero-error/run
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
cd /srv/zotero/dataserver/processor/error
|
||||||
|
exec 2>&1
|
||||||
|
exec chpst -u www-data:www-data php5 daemon.php
|
||||||
3
dataserver/sv/zotero-upload/log/run
Executable file
3
dataserver/sv/zotero-upload/log/run
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
exec svlogd /srv/zotero/log/upload
|
||||||
5
dataserver/sv/zotero-upload/run
Executable file
5
dataserver/sv/zotero-upload/run
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
cd /srv/zotero/dataserver/processor/upload
|
||||||
|
exec 2>&1
|
||||||
|
exec chpst -u www-data:www-data php5 daemon.php
|
||||||
33
mysql/setup_db
Executable file
33
mysql/setup_db
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
DB="mysql -h 127.0.0.1 -P 3306 -u root -ppassword"
|
||||||
|
|
||||||
|
echo "DROP DATABASE IF EXISTS zotero_master" | $DB
|
||||||
|
echo "DROP DATABASE IF EXISTS zotero_shards" | $DB
|
||||||
|
echo "DROP DATABASE IF EXISTS zotero_ids" | $DB
|
||||||
|
|
||||||
|
echo "CREATE DATABASE zotero_master" | $DB
|
||||||
|
echo "CREATE DATABASE zotero_shards" | $DB
|
||||||
|
echo "CREATE DATABASE zotero_ids" | $DB
|
||||||
|
|
||||||
|
echo "DROP USER zotero@localhost;" | $DB
|
||||||
|
|
||||||
|
echo "CREATE USER zotero@localhost IDENTIFIED BY 'foobar';" | $DB
|
||||||
|
|
||||||
|
echo "GRANT SELECT, INSERT, UPDATE, DELETE ON zotero_master.* TO zotero@localhost;" | $DB
|
||||||
|
echo "GRANT SELECT, INSERT, UPDATE, DELETE ON zotero_shards.* TO zotero@localhost;" | $DB
|
||||||
|
echo "GRANT SELECT,INSERT,DELETE ON zotero_ids.* TO zotero@localhost;" | $DB
|
||||||
|
|
||||||
|
# Load in master schema
|
||||||
|
$DB zotero_master < master.sql
|
||||||
|
$DB zotero_master < coredata.sql
|
||||||
|
|
||||||
|
# Set up shard info
|
||||||
|
echo "INSERT INTO shardHosts VALUES (1, '127.0.0.1', 3306, 'up');" | $DB zotero_master
|
||||||
|
echo "INSERT INTO shards VALUES (1, 1, 'zotero_shards', 'up', 0);" | $DB zotero_master
|
||||||
|
|
||||||
|
# Load in shard schema
|
||||||
|
cat shard.sql | $DB zotero_shards
|
||||||
|
cat triggers.sql | $DB zotero_shards
|
||||||
|
|
||||||
|
# Load in schema on id server
|
||||||
|
cat ids.sql | $DB zotero_ids
|
||||||
6
mysql/zotero.cnf
Normal file
6
mysql/zotero.cnf
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[mysqld]
|
||||||
|
character-set-server = utf8
|
||||||
|
collation-server = utf8_general_ci
|
||||||
|
event-scheduler = ON
|
||||||
|
sql-mode = STRICT_ALL_TABLES
|
||||||
|
default-time-zone = '+0:00'
|
||||||
22
patches/add_user
Executable file
22
patches/add_user
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?
|
||||||
|
set_include_path("../include");
|
||||||
|
require("header.inc.php");
|
||||||
|
|
||||||
|
if (empty($argv[1]) || empty($argv[2]) || empty($argv[3])) {
|
||||||
|
die("Usage: $argv[0] " . '$userID $username $password' . "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$userID = $argv[1];
|
||||||
|
$username = $argv[2];
|
||||||
|
$password = $argv[3];
|
||||||
|
$salt = Z_CONFIG::$AUTH_SALT;
|
||||||
|
|
||||||
|
echo "Adding new user $username with ID $userID\n";
|
||||||
|
Zotero_Users::add($userID, $username);
|
||||||
|
|
||||||
|
$hash = SHA1($salt . $password);
|
||||||
|
echo "$salt . $password $hash\n";
|
||||||
|
$sql = "update users set password=? where userid=?";
|
||||||
|
Zotero_DB::query($sql, array($hash, $userID));
|
||||||
|
?>
|
||||||
143
patches/uwsgi
Executable file
143
patches/uwsgi
Executable file
@@ -0,0 +1,143 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: uwsgi
|
||||||
|
# Required-Start: $local_fs $remote_fs $network
|
||||||
|
# Required-Stop: $local_fs $remote_fs $network
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: Start/stop uWSGI server instance(s)
|
||||||
|
# Description: This script manages uWSGI server instance(s).
|
||||||
|
# You could control specific instance(s) by issuing:
|
||||||
|
#
|
||||||
|
# service uwsgi <command> <confname> <confname> ...
|
||||||
|
#
|
||||||
|
# You can issue to init.d script following commands:
|
||||||
|
# * start | starts daemon
|
||||||
|
# * stop | stops daemon
|
||||||
|
# * reload | sends to daemon SIGHUP signal
|
||||||
|
# * force-reload | sends to daemon SIGTERM signal
|
||||||
|
# * restart | issues 'stop', then 'start' commands
|
||||||
|
# * status | shows status of daemon instance
|
||||||
|
#
|
||||||
|
# 'status' command must be issued with exactly one
|
||||||
|
# argument: '<confname>'.
|
||||||
|
#
|
||||||
|
# In init.d script output:
|
||||||
|
# * . -- command was executed without problems or instance
|
||||||
|
# is already in needed state
|
||||||
|
# * ! -- command failed (or executed with some problems)
|
||||||
|
# * ? -- configuration file for this instance isn't found
|
||||||
|
# and this instance is ignored
|
||||||
|
#
|
||||||
|
# For more details see /usr/share/doc/uwsgi/README.Debian.
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
# Author: Leonid Borisenko <leo.borisenko@gmail.com>
|
||||||
|
|
||||||
|
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||||
|
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||||
|
DESC="app server(s)"
|
||||||
|
NAME="uwsgi"
|
||||||
|
DAEMON="/usr/bin/uwsgi"
|
||||||
|
SCRIPTNAME="/etc/init.d/${NAME}"
|
||||||
|
|
||||||
|
UWSGI_CONFDIR="/etc/uwsgi"
|
||||||
|
UWSGI_APPS_CONFDIR_SUFFIX="s-enabled"
|
||||||
|
UWSGI_APPS_CONFDIR_GLOB="${UWSGI_CONFDIR}/app${UWSGI_APPS_CONFDIR_SUFFIX}"
|
||||||
|
|
||||||
|
UWSGI_RUNDIR="/run/uwsgi"
|
||||||
|
|
||||||
|
# Configuration namespace is used as name of runtime and log subdirectory.
|
||||||
|
# uWSGI instances sharing the same app configuration directory also shares
|
||||||
|
# the same runtime and log subdirectory.
|
||||||
|
#
|
||||||
|
# When init.d script cannot detect namespace for configuration file, default
|
||||||
|
# namespace will be used.
|
||||||
|
UWSGI_DEFAULT_CONFNAMESPACE=app
|
||||||
|
|
||||||
|
# Exit if the package is not installed
|
||||||
|
[ -x "$DAEMON" ] || exit 0
|
||||||
|
|
||||||
|
# Load the VERBOSE setting and other rcS variables
|
||||||
|
. /lib/init/vars.sh
|
||||||
|
|
||||||
|
# Read configuration variable file if it is present
|
||||||
|
[ -r "/etc/default/${NAME}" ] && . "/etc/default/${NAME}"
|
||||||
|
|
||||||
|
# Define LSB log_* functions.
|
||||||
|
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
# Define supplementary functions
|
||||||
|
. /usr/share/uwsgi/init/snippets
|
||||||
|
. /usr/share/uwsgi/init/do_command
|
||||||
|
|
||||||
|
WHAT=$1
|
||||||
|
shift
|
||||||
|
case "$WHAT" in
|
||||||
|
start)
|
||||||
|
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||||
|
do_command "$WHAT" "$@"
|
||||||
|
RETVAL="$?"
|
||||||
|
RETVAL=0
|
||||||
|
[ "$VERBOSE" != no ] && log_end_msg "$RETVAL"
|
||||||
|
;;
|
||||||
|
|
||||||
|
stop)
|
||||||
|
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||||
|
do_command "$WHAT" "$@"
|
||||||
|
RETVAL="$?"
|
||||||
|
[ "$VERBOSE" != no ] && log_end_msg "$RETVAL"
|
||||||
|
;;
|
||||||
|
|
||||||
|
status)
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
[ "$VERBOSE" != no ] && log_failure_msg "which one?"
|
||||||
|
else
|
||||||
|
PIDFILE="$(
|
||||||
|
find_specific_pidfile "$(relative_path_to_conffile_with_spec "$1")"
|
||||||
|
)"
|
||||||
|
status_of_proc -p "$PIDFILE" "$DAEMON" "$NAME" \
|
||||||
|
&& exit 0 \
|
||||||
|
|| exit $?
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
reload)
|
||||||
|
[ "$VERBOSE" != no ] && log_daemon_msg "Reloading $DESC" "$NAME"
|
||||||
|
do_command "$WHAT" "$@"
|
||||||
|
RETVAL="$?"
|
||||||
|
[ "$VERBOSE" != no ] && log_end_msg "$RETVAL"
|
||||||
|
;;
|
||||||
|
|
||||||
|
force-reload)
|
||||||
|
[ "$VERBOSE" != no ] && log_daemon_msg "Forced reloading $DESC" "$NAME"
|
||||||
|
do_command "$WHAT" "$@"
|
||||||
|
RETVAL="$?"
|
||||||
|
[ "$VERBOSE" != no ] && log_end_msg "$RETVAL"
|
||||||
|
;;
|
||||||
|
|
||||||
|
restart)
|
||||||
|
[ "$VERBOSE" != no ] && log_daemon_msg "Restarting $DESC" "$NAME"
|
||||||
|
CURRENT_VERBOSE=$VERBOSE
|
||||||
|
VERBOSE=no
|
||||||
|
do_command stop "$@"
|
||||||
|
VERBOSE=$CURRENT_VERBOSE
|
||||||
|
case "$?" in
|
||||||
|
0)
|
||||||
|
do_command start "$@"
|
||||||
|
RETVAL="$?"
|
||||||
|
[ "$VERBOSE" != no ] && log_end_msg "$RETVAL"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Failed to stop
|
||||||
|
[ "$VERBOSE" != no ] && log_end_msg 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload|force-reload}" >&2
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
512
zss/ZSS.pm
Normal file
512
zss/ZSS.pm
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
package ZSS;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Plack::Request;
|
||||||
|
use Digest::HMAC_SHA1 qw(hmac_sha1);
|
||||||
|
use Digest::MD5 qw (md5_base64);
|
||||||
|
use MIME::Base64 qw(decode_base64 encode_base64);
|
||||||
|
use JSON::XS;
|
||||||
|
use Date::Parse;
|
||||||
|
use URI;
|
||||||
|
use URI::QueryParam;
|
||||||
|
use URI::Escape;
|
||||||
|
use Switch;
|
||||||
|
use Encode;
|
||||||
|
use Try::Tiny;
|
||||||
|
|
||||||
|
use ZSS::Store;
|
||||||
|
|
||||||
|
use Data::Dumper qw(Dumper);
|
||||||
|
$Data::Dumper::Sortkeys = 1;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ($class) = @_;
|
||||||
|
|
||||||
|
# TODO: read from config
|
||||||
|
my $self = {};
|
||||||
|
|
||||||
|
$self->{buckets}->{zotero}->{secretkey} = "yoursecretkey";
|
||||||
|
$self->{buckets}->{zotero}->{store} = ZSS::Store->new("/srv/zotero/storage/");
|
||||||
|
|
||||||
|
bless $self, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub respond {
|
||||||
|
my $code = shift;
|
||||||
|
my $msg = shift;
|
||||||
|
return [ $code, [ 'Content-Type' => 'text/plain', 'Content-Length' => length($msg)], [$msg] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub xml2string {
|
||||||
|
my $xml = shift;
|
||||||
|
|
||||||
|
my $msg = '';
|
||||||
|
|
||||||
|
while (my $token = shift @{$xml}) {
|
||||||
|
my $data = shift @{$xml};
|
||||||
|
$msg .= '<'.$token.'>';
|
||||||
|
if (ref $data eq 'ARRAY') {
|
||||||
|
$msg .= xml2string($data);
|
||||||
|
} else {
|
||||||
|
$msg .= $data;
|
||||||
|
}
|
||||||
|
$msg .= '</'.$token.'>';
|
||||||
|
}
|
||||||
|
return $msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub respondXML {
|
||||||
|
my $code = shift;
|
||||||
|
my $xml = shift;
|
||||||
|
|
||||||
|
return [ $code, [ 'Content-Type' => 'application/xml'], ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".xml2string($xml)] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_policy {
|
||||||
|
my ($self, $cmp, $key, $val) = @_;
|
||||||
|
|
||||||
|
switch ($cmp) {
|
||||||
|
case 'eq' {
|
||||||
|
if ($key eq 'bucket') {
|
||||||
|
$key = $self->{request}->{bucket};
|
||||||
|
} else {
|
||||||
|
# TODO: Replace Plack::Request
|
||||||
|
$key = $self->{req}->parameters->get($key)
|
||||||
|
}
|
||||||
|
return 1 if $key eq $val;
|
||||||
|
};
|
||||||
|
case 'content-length-range' {
|
||||||
|
my $len = $self->{request}->{env}->{CONTENT_LENGTH};
|
||||||
|
# $self->log("Length: ".$len.", Limits: ".$key.", ".$val);
|
||||||
|
return 1 if (($len > $key) && ($len < $val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub log {
|
||||||
|
my ($self, $msg) = @_;
|
||||||
|
|
||||||
|
$self->{request}->{env}->{'psgix.logger'}->({ level => 'debug', message => $msg });
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_signature {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $request = $self->{request};
|
||||||
|
my $env = $request->{env};
|
||||||
|
|
||||||
|
my $secret = $self->{buckets}->{$request->{bucket}}->{secretkey};
|
||||||
|
|
||||||
|
my $query = {};
|
||||||
|
my $use_query = undef;
|
||||||
|
|
||||||
|
if ($env->{QUERY_STRING}) {
|
||||||
|
$query = $request->{uri}->query_form_hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($query->{Signature}) {
|
||||||
|
$use_query = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# X-AMZ headers or query parameters
|
||||||
|
my $amzstring = '';
|
||||||
|
if ($use_query) {
|
||||||
|
for my $key (sort(grep(/^x-amz/, keys %$query))) {
|
||||||
|
my $value = $query->{$key};
|
||||||
|
$amzstring .= lc($key).":".$value."\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for my $key (sort(grep(/^HTTP_X_AMZ/, keys %$env))) {
|
||||||
|
next if ($key eq 'HTTP_X_AMZ_DATE');
|
||||||
|
my $value = $env->{$key};
|
||||||
|
$key =~ s/_/-/g;
|
||||||
|
$amzstring .= lc(substr($key,5)).":".$value."\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Date Header or Expires query parameter
|
||||||
|
my $date;
|
||||||
|
if ($use_query) {
|
||||||
|
$date = $query->{Expires};
|
||||||
|
} else {
|
||||||
|
if ($env->{HTTP_X_AMZ_DATE}) {
|
||||||
|
$date = $env->{HTTP_X_AMZ_DATE};
|
||||||
|
} else {
|
||||||
|
$date = $env->{HTTP_DATE};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# changing response headers parameters
|
||||||
|
my $additional_params = '';
|
||||||
|
if ($query) {
|
||||||
|
my $sep = '?';
|
||||||
|
my @params = qw(response-cache-control response-content-disposition response-content-encoding response-content-language response-content-type response-expires);
|
||||||
|
for my $key (@params) {
|
||||||
|
if ($query->{$key}) {
|
||||||
|
$additional_params .= $sep.$key."=".$query->{$key};
|
||||||
|
$sep = '&' if ($sep eq '?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $stringtosign = $env->{REQUEST_METHOD}."\n".
|
||||||
|
($env->{HTTP_CONTENT_MD5} || '')."\n".
|
||||||
|
($env->{CONTENT_TYPE} || '')."\n".
|
||||||
|
($date || '')."\n".
|
||||||
|
$amzstring.
|
||||||
|
"/".$request->{bucket}."/".$request->{key_escaped}.$additional_params;
|
||||||
|
|
||||||
|
# $self->log("Stringtosign:".$stringtosign."End");
|
||||||
|
|
||||||
|
return encode_base64(hmac_sha1($stringtosign, $secret), '');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_signature {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $request = $self->{request};
|
||||||
|
my $env = $request->{env};
|
||||||
|
|
||||||
|
my $received_signature;
|
||||||
|
|
||||||
|
if ($env->{QUERY_STRING} eq '') {
|
||||||
|
($received_signature) = ($env->{HTTP_AUTHORIZATION} || '') =~ m/^AWS .*:(.*)$/;
|
||||||
|
} else {
|
||||||
|
$received_signature = $request->{uri}->query_param('Signature') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
unless ($received_signature) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $signature = $self->get_signature();
|
||||||
|
|
||||||
|
# $self->log("Check Signature: $received_signature == $signature");
|
||||||
|
|
||||||
|
return ($signature eq $received_signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_POST {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $request = $self->{request};
|
||||||
|
my $env = $request->{env};
|
||||||
|
|
||||||
|
my $req = $self->{req};
|
||||||
|
|
||||||
|
my $policy = $req->parameters->get('policy');
|
||||||
|
my $signature = $req->parameters->get('signature');
|
||||||
|
|
||||||
|
unless ($signature && $policy) {
|
||||||
|
return respondXML(400, ['Error' => ['Code' => 'InvalidPolicyDocument']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
unless ($signature eq encode_base64(hmac_sha1($policy, $self->{buckets}->{$request->{bucket}}->{secretkey}), '')) {
|
||||||
|
return respondXML(403, ['Error' => ['Code' => 'SignatureDoesNotMatch']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $json;
|
||||||
|
try {
|
||||||
|
$json = JSON::XS->new->relaxed->decode(decode_base64($policy));
|
||||||
|
} catch {
|
||||||
|
return respondXML(400, ['Error' => ['Code' => 'InvalidPolicyDocument']]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return respondXML(400, ['Error' => ['Code' => 'InvalidPolicyDocument', 'Message' => 'No expiration time specified in policy document']]) unless (defined $json->{expiration});
|
||||||
|
|
||||||
|
my $expiration = Date::Parse::str2time($json->{expiration});
|
||||||
|
|
||||||
|
if ($self->{request}->{starttime} > $expiration) {
|
||||||
|
return respondXML(400, ['Error' => ['Code' => 'ExpiredToken']]);
|
||||||
|
}
|
||||||
|
# $self->log("Expires:".$expiration."; Starttime:".$self->{request}->{starttime});
|
||||||
|
|
||||||
|
foreach my $ref (@{$json->{conditions}}) {
|
||||||
|
if (ref $ref eq 'HASH') {
|
||||||
|
foreach my $key (keys %{$ref}) {
|
||||||
|
my $val = encode("utf8", $$ref{$key}); #TODO: better to decode parameter? Is unicode normalization required?
|
||||||
|
my $result = $self->check_policy('eq', $key, $val);
|
||||||
|
# $self->log($key."=".$val."(".$result.")");
|
||||||
|
unless ($result) {return respondXML(400, ['Error' => ['Code' => 'InvalidPolicyDocument']])};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ref $ref eq 'ARRAY') {
|
||||||
|
my $key = $$ref[1];
|
||||||
|
$key =~ s/^\$//;
|
||||||
|
my $val = encode("utf8", $$ref[2]); #TODO: better to decode parameter? Is unicode normalization required?
|
||||||
|
my $result = $self->check_policy($$ref[0], $key, $val);
|
||||||
|
# $self->log($key." ".$$ref[0]." ".$val." (".$result.")");
|
||||||
|
unless ($result) {return respondXML(400, ['Error' => ['Code' => 'InvalidPolicyDocument']])};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $data = $req->parameters->get('file');
|
||||||
|
unless ($data) {
|
||||||
|
return respondXML(400, ['Error' => ['Code' => 'IncorrectNumberOfFilesInPostRequest']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $md5 = md5_base64($data);
|
||||||
|
unless ($req->parameters->get('Content-MD5') eq $md5.'==') {
|
||||||
|
return respondXML(400, ['Error' => ['Code' => 'BadDigest']])
|
||||||
|
}
|
||||||
|
|
||||||
|
my $key = $req->parameters->get('key');
|
||||||
|
my $store = $self->{buckets}->{$request->{bucket}}->{store};
|
||||||
|
|
||||||
|
my $meta = {
|
||||||
|
'md5' => unpack('H*', decode_base64($md5)),
|
||||||
|
'acl' => $self->{req}->parameters->get('acl') || 'private'
|
||||||
|
};
|
||||||
|
|
||||||
|
$store->store_file($key, $req->parameters->get('file'), JSON::XS->new->utf8->encode($meta));
|
||||||
|
|
||||||
|
my $status = $req->parameters->get('success_action_status');
|
||||||
|
$status = '403' unless (($status eq '200') || ($status eq '201'));
|
||||||
|
|
||||||
|
# TODO: access_action_redirect
|
||||||
|
|
||||||
|
return respond($status, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_HEAD {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $request = $self->{request};
|
||||||
|
my $env = $request->{env};
|
||||||
|
my $key = $request->{key};
|
||||||
|
|
||||||
|
my $store = $self->{buckets}->{$request->{bucket}}->{store};
|
||||||
|
|
||||||
|
unless ($store->check_exists($key)) {
|
||||||
|
return respondXML(404, ['Error' => ['Code' => 'NoSuchKey']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $meta;
|
||||||
|
try {
|
||||||
|
$meta = JSON::XS->new->utf8->decode($store->retrieve_filemeta($key));
|
||||||
|
};
|
||||||
|
unless (ref($meta) eq 'HASH') {
|
||||||
|
$meta = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $headers = ['Content-Length' => $store->get_size($key)];
|
||||||
|
if ($meta->{type}) {
|
||||||
|
push @$headers, 'Content-Type';
|
||||||
|
push @$headers, $meta->{type};
|
||||||
|
}
|
||||||
|
if ($meta->{md5}) {
|
||||||
|
push @$headers, 'ETag';
|
||||||
|
push @$headers, "\"".$meta->{md5}."\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
return [200, $headers, []];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_GET {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $request = $self->{request};
|
||||||
|
my $env = $request->{env};
|
||||||
|
|
||||||
|
my $key = $request->{key};
|
||||||
|
|
||||||
|
my $store = $self->{buckets}->{$request->{bucket}}->{store};
|
||||||
|
|
||||||
|
unless($store->check_exists($key)){
|
||||||
|
return respondXML(404, ['Error' => ['Code' => 'NoSuchKey']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $meta;
|
||||||
|
try {
|
||||||
|
$meta = JSON::XS->new->utf8->decode($store->retrieve_filemeta($key));
|
||||||
|
};
|
||||||
|
unless (ref($meta) eq 'HASH') {
|
||||||
|
$meta = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $headers = ['Content-Length' => $store->get_size($key)];
|
||||||
|
my $ct = $request->{uri}->query_param('response-content-type');
|
||||||
|
$ct = $meta->{type} unless ($ct);
|
||||||
|
if ($ct) {
|
||||||
|
push @$headers, 'Content-Type';
|
||||||
|
push @$headers, $ct;
|
||||||
|
}
|
||||||
|
if ($meta->{md5}) {
|
||||||
|
push @$headers, 'ETag';
|
||||||
|
push @$headers, "\"".$meta->{md5}."\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
return [200, $headers, $store->retrieve_file($key)];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub handle_PUT {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $request = $self->{request};
|
||||||
|
my $env = $request->{env};
|
||||||
|
my $store = $self->{buckets}->{$request->{bucket}}->{store};
|
||||||
|
|
||||||
|
my $key = $request->{key};
|
||||||
|
|
||||||
|
my $cl = $env->{CONTENT_LENGTH};
|
||||||
|
my $source = $env->{HTTP_X_AMZ_COPY_SOURCE};
|
||||||
|
|
||||||
|
if (($cl == 0) && ($source)) {
|
||||||
|
# Copy File
|
||||||
|
$source = uri_unescape($source);
|
||||||
|
(my $sourceBucket, my $sourceKey) = $source =~ m/^\/([^\?\/]*)\/?([^\?]*)/;
|
||||||
|
|
||||||
|
# $self->log("Source: ".$sourceBucket."/bla/".$sourceKey."\nDestinationKey: ".$key."\n");
|
||||||
|
|
||||||
|
my $res = $store->link_files($sourceKey, $key);
|
||||||
|
|
||||||
|
if ($res) {
|
||||||
|
|
||||||
|
my $meta;
|
||||||
|
try {
|
||||||
|
$meta = JSON::XS->new->utf8->decode($store->retrieve_filemeta($key));
|
||||||
|
};
|
||||||
|
unless (ref($meta) eq 'HASH') {
|
||||||
|
$meta = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return respondXML(200, ['CopyObjectResult' => [ 'LastModified' => '2012', 'ETag' => $meta->{md5}]]);
|
||||||
|
} else {
|
||||||
|
return respondXML(500, ['Error' => ['Code' => 'InternalError']]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# Normal PUT
|
||||||
|
my $input = $env->{'psgi.input'};
|
||||||
|
my $cl = $env->{CONTENT_LENGTH};
|
||||||
|
my $data;
|
||||||
|
|
||||||
|
if (($input->read($data, $cl)) != $cl) {
|
||||||
|
return respondXML(400, ['Error' => ['Code' => 'IncompleteBody']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $md5 = md5_base64($data);
|
||||||
|
|
||||||
|
my $meta = {};
|
||||||
|
$meta->{type} = $env->{CONTENT_TYPE} if ($env->{CONTENT_TYPE});
|
||||||
|
$meta->{acl} = $env->{HTTP_X_AMZ_ACL} || 'private';
|
||||||
|
$meta->{md5} = unpack('H*', decode_base64($md5));
|
||||||
|
|
||||||
|
if ($env->{HTTP_CONTENT_MD5}) {
|
||||||
|
return respondXML(400, ['Error' => ['Code' => 'BadDigest']]) unless ($env->{HTTP_CONTENT_MD5} eq $md5.'==');
|
||||||
|
}
|
||||||
|
|
||||||
|
$store->store_file($key, $data, JSON::XS->new->utf8->encode($meta));
|
||||||
|
|
||||||
|
return respond(200, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_DELETE {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
my $request = $self->{request};
|
||||||
|
my $env = $request->{env};
|
||||||
|
my $store = $self->{buckets}->{$request->{bucket}}->{store};
|
||||||
|
|
||||||
|
|
||||||
|
my $key = $request->{key};
|
||||||
|
|
||||||
|
unless ($store->check_exists($key)) {
|
||||||
|
return respondXML(404, ['Error' => ['Code' => 'NoSuchKey', 'Message' => 'The resource you requested does not exist', 'Resource' => $key]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($store->delete_file($key)) {
|
||||||
|
return [204, [], []];
|
||||||
|
} else {
|
||||||
|
return respondXML(500, ['Error' => ['Code' => 'InternalError']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub request_uri {
|
||||||
|
my $env = shift;
|
||||||
|
|
||||||
|
my $uri = ($env->{'psgi.url_scheme'} || "http") .
|
||||||
|
"://" .
|
||||||
|
($env->{HTTP_HOST} || (($env->{SERVER_NAME} || "") . ":" . ($env->{SERVER_PORT} || 80))) .
|
||||||
|
($env->{SCRIPT_NAME} || "");
|
||||||
|
|
||||||
|
return URI->new($uri . $env->{REQUEST_URI})->canonical();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle {
|
||||||
|
my ($self, $env) = @_;
|
||||||
|
|
||||||
|
my $request = {};
|
||||||
|
|
||||||
|
$request->{env} = $env;
|
||||||
|
$request->{starttime} = time();
|
||||||
|
|
||||||
|
$request->{uri} = request_uri($env);
|
||||||
|
|
||||||
|
# split in bucket and key (currently only path style buckets no host style)
|
||||||
|
($request->{bucket}, $request->{key_escaped}) = $env->{REQUEST_URI} =~ m/^\/([^\?\/]*)\/?([^\?]*)/;
|
||||||
|
$request->{key} = uri_unescape($request->{key_escaped}) || '';
|
||||||
|
|
||||||
|
return respond(200, "Nothing to see here") if ($request->{bucket} eq '');
|
||||||
|
|
||||||
|
if (not defined $self->{buckets}->{$request->{bucket}}) {
|
||||||
|
return respondXML(404,
|
||||||
|
['Error' =>
|
||||||
|
['Code' => 'NoSuchBucket',
|
||||||
|
'Message' => 'The specified bucket does not exist',
|
||||||
|
'BucketName' => $request->{bucket}]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{request} = $request;
|
||||||
|
|
||||||
|
# TODO: body parsing for POST. Parameter "file" should be saved as file instead of in memory
|
||||||
|
my $req = Plack::Request->new($env);
|
||||||
|
$self->{req} = $req;
|
||||||
|
|
||||||
|
my @methods = qw(POST GET HEAD PUT DELETE);
|
||||||
|
|
||||||
|
unless ($env->{REQUEST_METHOD} ~~ @methods) {
|
||||||
|
undef($self->{request});
|
||||||
|
|
||||||
|
return respondXML(405,
|
||||||
|
['Error' =>
|
||||||
|
['Code' => 'MethodNotAllowed',
|
||||||
|
'Message' => 'The specified method is not allowed']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $result;
|
||||||
|
if ($env->{REQUEST_METHOD} eq 'POST') {
|
||||||
|
$result = $self->handle_POST();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
unless ($self->check_signature()) {
|
||||||
|
undef($self->{request});
|
||||||
|
return respondXML(403, ['Error' => ['Code' => 'SignatureDoesNotMatch']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $method = 'handle_'.$env->{REQUEST_METHOD};
|
||||||
|
$result = $self->$method;
|
||||||
|
}
|
||||||
|
|
||||||
|
undef($self->{request});
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
sub psgi_callback {
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
sub {
|
||||||
|
$self->handle( shift );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
164
zss/ZSS/Store.pm
Normal file
164
zss/ZSS/Store.pm
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package ZSS::Store;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Digest::MD5 qw (md5_hex);
|
||||||
|
use File::Util qw(escape_filename);
|
||||||
|
use File::Path qw(make_path);
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my $class = shift;
|
||||||
|
|
||||||
|
# TODO: read from config
|
||||||
|
my $self = {storagepath => shift};
|
||||||
|
|
||||||
|
bless $self, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_path {
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
my $dirname = md5_hex($key);
|
||||||
|
|
||||||
|
my $dir = $self->{storagepath} . substr($dirname, 0, 1) . "/" . $dirname ."/";
|
||||||
|
|
||||||
|
return $dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_filename {
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
return escape_filename($key, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_filepath {
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
return $self->get_path($key) . $self->get_filename($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub store_file {
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
my $data = shift;
|
||||||
|
my $meta = shift;
|
||||||
|
|
||||||
|
my $dir = $self->get_path($key);
|
||||||
|
my $file = $self->get_filename($key);
|
||||||
|
|
||||||
|
make_path($dir);
|
||||||
|
|
||||||
|
# Write data to temp file and rename to the desired name
|
||||||
|
# This only changes this file and not other hardlinks
|
||||||
|
open(my $fh, '>:raw', $dir.$file.".temp");
|
||||||
|
print $fh ($data);
|
||||||
|
close($fh);
|
||||||
|
rename($dir.$file.".temp", $dir.$file);
|
||||||
|
|
||||||
|
if ($meta) {
|
||||||
|
open($fh, '>:raw', $dir.$file.".meta.temp");
|
||||||
|
print $fh ($meta);
|
||||||
|
close($fh);
|
||||||
|
rename($dir.$file.".meta.temp", $dir.$file.".meta");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_exists{
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
my $path = $self->get_filepath($key);
|
||||||
|
unless (-e $path){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub retrieve_file {
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
unless($self->check_exists($key)){
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
my $path = $self->get_filepath($key);
|
||||||
|
open(my $fh, '<:raw', $path);
|
||||||
|
return $fh;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub retrieve_filemeta {
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
unless($self->check_exists($key)){
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
my $metafile = $self->get_filepath($key) . ".meta";
|
||||||
|
|
||||||
|
# check if metadata is present
|
||||||
|
unless (-e $metafile) {
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
# limt size of metadata to 8kB
|
||||||
|
my $size = -s $metafile;
|
||||||
|
unless ($size <= 8192) {
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $meta;
|
||||||
|
open(my $fh, '<:raw', $metafile);
|
||||||
|
read ($fh, $meta, $size);
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_size{
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
my $path = $self->get_filepath($key);
|
||||||
|
|
||||||
|
unless (-e $path) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
my $size = -s $path;
|
||||||
|
return $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub link_files{
|
||||||
|
my $self = shift;
|
||||||
|
my $source_key = shift;
|
||||||
|
my $destination_key = shift;
|
||||||
|
|
||||||
|
my $source_path = $self->get_filepath($source_key);
|
||||||
|
my $destination_dir = $self->get_path($destination_key);
|
||||||
|
my $destination_path = $self->get_filepath($destination_key);
|
||||||
|
|
||||||
|
make_path($destination_dir);
|
||||||
|
|
||||||
|
link($source_path.".meta", $destination_path.".meta");
|
||||||
|
|
||||||
|
return link($source_path, $destination_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub delete_file{
|
||||||
|
my $self = shift;
|
||||||
|
my $key = shift;
|
||||||
|
|
||||||
|
my $dir = $self->get_path($key);
|
||||||
|
my $file = $self->get_filename($key);
|
||||||
|
|
||||||
|
# Remove metadata
|
||||||
|
unlink($dir.$file.".meta");
|
||||||
|
|
||||||
|
unless (unlink($dir.$file)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return rmdir($dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
11
zss/zss.psgi
Normal file
11
zss/zss.psgi
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib ('/srv/zotero/zss/');
|
||||||
|
|
||||||
|
use ZSS;
|
||||||
|
|
||||||
|
my $app = ZSS->new();
|
||||||
|
|
||||||
|
$app->psgi_callback();
|
||||||
3
zss/zss.yaml
Normal file
3
zss/zss.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
uwsgi:
|
||||||
|
plugin: psgi
|
||||||
|
psgi: /srv/zotero/zss/zss.psgi
|
||||||
Reference in New Issue
Block a user