mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-26 09:05:10 +00:00
armv64 support
This commit is contained in:
parent
47d7003a28
commit
3332996dfa
39 changed files with 1767 additions and 32 deletions
|
|
@ -1,8 +1,8 @@
|
|||
# Frappe Bench Dockerfile
|
||||
FROM bitnami/minideb:latest
|
||||
FROM debian:stable-slim
|
||||
LABEL author=frappé
|
||||
|
||||
RUN install_packages \
|
||||
RUN apt-get update -y && apt-get install \
|
||||
git \
|
||||
wkhtmltopdf \
|
||||
mariadb-client \
|
||||
|
|
@ -49,7 +49,7 @@ RUN install_packages \
|
|||
python3-setuptools \
|
||||
python3-tk \
|
||||
python-virtualenv \
|
||||
less
|
||||
less -y && apt-get clean
|
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
|
||||
&& dpkg-reconfigure --frontend=noninteractive locales
|
||||
|
|
@ -85,7 +85,7 @@ RUN bash -c "bench --version"
|
|||
# https://nodejs.org/download/release/latest-v10.x/
|
||||
# https://nodejs.org/download/release/latest-v12.x/
|
||||
# https://nodejs.org/download/release/latest-v13.x/
|
||||
ENV NODE_VERSION=12.18.2
|
||||
ENV NODE_VERSION=12.19.1
|
||||
ENV NODE_VERSION_FRAPPEV11=10.21.0
|
||||
|
||||
# Install nvm with node
|
||||
0
build/common/common_site_config.json.template
Executable file → Normal file
0
build/common/common_site_config.json.template
Executable file → Normal file
0
build/common/worker/bench
Executable file → Normal file
0
build/common/worker/bench
Executable file → Normal file
0
build/common/worker/docker-entrypoint.sh
Executable file → Normal file
0
build/common/worker/docker-entrypoint.sh
Executable file → Normal file
0
build/common/worker/healthcheck.sh
Executable file → Normal file
0
build/common/worker/healthcheck.sh
Executable file → Normal file
0
build/common/worker/install_app.sh
Executable file → Normal file
0
build/common/worker/install_app.sh
Executable file → Normal file
|
|
@ -1,13 +1,14 @@
|
|||
ARG NODE_IMAGE_TAG=12-prod
|
||||
ARG NODE_IMAGE_TAG=12.19.1-buster-slim
|
||||
ARG GIT_BRANCH=develop
|
||||
FROM bitnami/node:${NODE_IMAGE_TAG}
|
||||
FROM node:${NODE_IMAGE_TAG}
|
||||
|
||||
ARG GIT_BRANCH
|
||||
COPY build/erpnext-nginx/install_app.sh /install_app
|
||||
COPY install_app.sh /install_app
|
||||
RUN chmod +x /install_app && apt-get update -y && apt-get install build-essential git python2 -y
|
||||
|
||||
RUN /install_app erpnext https://github.com/frappe/erpnext ${GIT_BRANCH}
|
||||
|
||||
FROM frappe/frappe-nginx:${GIT_BRANCH}
|
||||
FROM frappe/frappe-nginx-arm:${GIT_BRANCH}
|
||||
|
||||
COPY --from=0 /home/frappe/frappe-bench/sites/ /var/www/html/
|
||||
COPY --from=0 /rsync /rsync
|
||||
2
build/erpnext-nginx/install_app.sh → build/erpnext-nginx-arm/install_app.sh
Executable file → Normal file
2
build/erpnext-nginx/install_app.sh → build/erpnext-nginx-arm/install_app.sh
Executable file → Normal file
|
|
@ -10,7 +10,7 @@ mkdir -p /home/frappe/frappe-bench/sites/assets
|
|||
cd /home/frappe/frappe-bench
|
||||
echo -e "frappe\n${APP_NAME}" > /home/frappe/frappe-bench/sites/apps.txt
|
||||
|
||||
install_packages git python2
|
||||
# install_packages git python2
|
||||
|
||||
mkdir -p apps
|
||||
cd apps
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
ARG GIT_BRANCH=develop
|
||||
FROM frappe/frappe-worker:${GIT_BRANCH}
|
||||
FROM frappe/frappe-worker-arm:${GIT_BRANCH}
|
||||
|
||||
ARG GIT_BRANCH
|
||||
RUN pip install bootstrapped --no-deps
|
||||
RUN install_app erpnext https://github.com/frappe/erpnext ${GIT_BRANCH}
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
# This is done to ensures that node-sass binary remains common.
|
||||
# node-sass is required to enable website theme feature used
|
||||
# by Website Manager role in Frappe Framework
|
||||
FROM bitnami/python:3.7-prod
|
||||
FROM python:3.7.9-slim-buster
|
||||
|
||||
ENV NVM_DIR=/root/.nvm
|
||||
ENV NODE_VERSION=12.18.3
|
||||
ENV NODE_VERSION=12.19.1
|
||||
ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
|
||||
RUN install_packages wget \
|
||||
RUN apt-get update -y && apt-get install wget python2 git -y build-essential \
|
||||
&& wget https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh \
|
||||
&& chmod +x install.sh \
|
||||
&& ./install.sh \
|
||||
|
|
@ -18,7 +18,7 @@ WORKDIR /home/frappe/frappe-bench
|
|||
RUN mkdir -p /home/frappe/frappe-bench/sites \
|
||||
&& echo "frappe" > /home/frappe/frappe-bench/sites/apps.txt
|
||||
|
||||
RUN install_packages git
|
||||
# RUN install_packages git
|
||||
|
||||
ARG GIT_BRANCH=develop
|
||||
RUN mkdir -p apps sites/assets/css \
|
||||
|
|
@ -45,8 +45,8 @@ RUN cp -R /home/frappe/frappe-bench/apps/frappe/frappe/public/* /home/frappe/fra
|
|||
FROM nginx:latest
|
||||
COPY --from=0 /home/frappe/frappe-bench/sites /var/www/html/
|
||||
COPY --from=0 /var/www/error_pages /var/www/
|
||||
COPY build/common/nginx-default.conf.template /etc/nginx/conf.d/default.conf.template
|
||||
COPY build/frappe-nginx/docker-entrypoint.sh /
|
||||
COPY nginx-default.conf.template /etc/nginx/conf.d/default.conf.template
|
||||
COPY docker-entrypoint.sh /
|
||||
|
||||
RUN apt-get update && apt-get install -y rsync && apt-get clean \
|
||||
&& echo "#!/bin/bash" > /rsync \
|
||||
0
build/frappe-nginx/docker-entrypoint.sh → build/frappe-nginx-arm/docker-entrypoint.sh
Executable file → Normal file
0
build/frappe-nginx/docker-entrypoint.sh → build/frappe-nginx-arm/docker-entrypoint.sh
Executable file → Normal file
104
build/frappe-nginx-arm/nginx-default.conf.template
Normal file
104
build/frappe-nginx-arm/nginx-default.conf.template
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
upstream frappe-server {
|
||||
server ${FRAPPE_PY}:${FRAPPE_PY_PORT} fail_timeout=0;
|
||||
}
|
||||
|
||||
upstream socketio-server {
|
||||
server ${FRAPPE_SOCKETIO}:${SOCKETIO_PORT} fail_timeout=0;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name $http_host;
|
||||
root /var/www/html;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
|
||||
location /assets {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~ ^/protected/(.*) {
|
||||
internal;
|
||||
try_files /sites/$http_host/$1 =404;
|
||||
}
|
||||
|
||||
location /socket.io {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header X-Frappe-Site-Name $host;
|
||||
proxy_set_header Origin $scheme://$http_host;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass http://socketio-server;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^(.+)/$ $1 permanent;
|
||||
rewrite ^(.+)/index\.html$ $1 permanent;
|
||||
rewrite ^(.+)\.html$ $1 permanent;
|
||||
|
||||
location ~ ^/files/.*.(htm|html|svg|xml) {
|
||||
add_header Content-disposition "attachment";
|
||||
try_files /sites/$http_host/public/$uri @webserver;
|
||||
}
|
||||
|
||||
try_files /sites/$http_host/public/$uri @webserver;
|
||||
}
|
||||
|
||||
location @webserver {
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Frappe-Site-Name $host;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Use-X-Accel-Redirect True;
|
||||
proxy_read_timeout 120;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_pass http://frappe-server;
|
||||
}
|
||||
|
||||
# error pages
|
||||
error_page 502 /502.html;
|
||||
location /502.html {
|
||||
root /var/www/templates;
|
||||
internal;
|
||||
}
|
||||
|
||||
# optimizations
|
||||
sendfile on;
|
||||
keepalive_timeout 15;
|
||||
client_max_body_size 50m;
|
||||
client_body_buffer_size 16K;
|
||||
client_header_buffer_size 1k;
|
||||
|
||||
# enable gzip compresion
|
||||
# based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge
|
||||
gzip on;
|
||||
gzip_http_version 1.1;
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 256;
|
||||
gzip_proxied any;
|
||||
gzip_vary on;
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/javascript
|
||||
application/json
|
||||
application/rss+xml
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
application/font-woff
|
||||
application/x-web-app-manifest+json
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/opentype
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
text/css
|
||||
text/plain
|
||||
text/x-component;
|
||||
# text/html is always compressed by HttpGzipModule
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ RUN mkdir -p /home/frappe/frappe-bench/sites /home/frappe/frappe-bench/apps/frap
|
|||
&& chown -R frappe:frappe /home/frappe
|
||||
|
||||
# Download socketio and purge curl package
|
||||
COPY build/frappe-socketio/package.json /home/frappe/frappe-bench/apps/frappe
|
||||
COPY package.json /home/frappe/frappe-bench/apps/frappe
|
||||
ARG GIT_BRANCH=develop
|
||||
RUN apt-get update && apt-get install -y curl \
|
||||
&& cd /home/frappe/frappe-bench/apps/frappe \
|
||||
|
|
@ -25,7 +25,7 @@ RUN cd /home/frappe/frappe-bench/apps/frappe \
|
|||
&& npm --version
|
||||
|
||||
# Setup docker-entrypoint
|
||||
COPY build/frappe-socketio/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN ln -s /usr/local/bin/docker-entrypoint.sh / # backwards compat
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench/sites
|
||||
0
build/frappe-socketio/docker-entrypoint.sh → build/frappe-socketio-arm/docker-entrypoint.sh
Executable file → Normal file
0
build/frappe-socketio/docker-entrypoint.sh → build/frappe-socketio-arm/docker-entrypoint.sh
Executable file → Normal file
|
|
@ -1,39 +1,42 @@
|
|||
FROM bitnami/python:3.7-prod
|
||||
FROM python:3.7.9-stretch
|
||||
|
||||
RUN useradd -ms /bin/bash frappe
|
||||
|
||||
ARG GIT_BRANCH=develop
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV NVM_DIR=/home/frappe/.nvm
|
||||
ENV NODE_VERSION=12.18.3
|
||||
ENV NODE_VERSION=12.19.1
|
||||
ENV PATH="/home/frappe/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
|
||||
|
||||
# Install dependencies
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
RUN install_packages \
|
||||
RUN apt-get update -y && apt-get install build-essential \
|
||||
git \
|
||||
mariadb-client \
|
||||
postgresql-client \
|
||||
gettext-base \
|
||||
wget \
|
||||
curl \
|
||||
# for PDF
|
||||
libjpeg62-turbo \
|
||||
libx11-6 \
|
||||
libxcb1 \
|
||||
libxext6 \
|
||||
libxrender1 \
|
||||
libffi-dev libjpeg-dev libxml2 \
|
||||
libssl-dev \
|
||||
fonts-cantarell \
|
||||
xfonts-75dpi \
|
||||
xfonts-base \
|
||||
zlib1g-dev \
|
||||
# For psycopg2
|
||||
libpq-dev \
|
||||
wait-for-it \
|
||||
&& wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.buster_amd64.deb \
|
||||
&& dpkg -i wkhtmltox_0.12.5-1.buster_amd64.deb && rm wkhtmltox_0.12.5-1.buster_amd64.deb \
|
||||
&& wget https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh \
|
||||
wait-for-it -y \
|
||||
&& wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_arm64.deb \
|
||||
&& dpkg -i wkhtmltox_0.12.6-1.buster_arm64.deb && rm wkhtmltox_0.12.6-1.buster_arm64.deb \
|
||||
&& wget https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh && chmod +x install.sh \
|
||||
&& apt-get purge -y wget && apt-get autoremove -y \
|
||||
&& chown -R frappe:frappe /home/frappe
|
||||
&& chown -R frappe:frappe /home/frappe && pip install bootstrapped --no-deps
|
||||
|
||||
USER frappe
|
||||
# Install nvm with node
|
||||
|
|
@ -55,14 +58,18 @@ RUN python -m venv env \
|
|||
|
||||
USER root
|
||||
# Copy scripts and templates
|
||||
COPY build/common/commands/* /home/frappe/frappe-bench/commands/
|
||||
COPY build/common/common_site_config.json.template /opt/frappe/common_site_config.json.template
|
||||
COPY build/common/worker/install_app.sh /usr/local/bin/install_app
|
||||
COPY build/common/worker/bench /usr/local/bin/bench
|
||||
COPY build/common/worker/healthcheck.sh /usr/local/bin/healthcheck.sh
|
||||
COPY common/commands/* /home/frappe/frappe-bench/commands/
|
||||
RUN chmod +x -R /home/frappe/frappe-bench/commands/
|
||||
COPY common/common_site_config.json.template /opt/frappe/common_site_config.json.template
|
||||
COPY common/worker/install_app.sh /usr/local/bin/install_app
|
||||
RUN chmod +x /usr/local/bin/install_app
|
||||
COPY common/worker/bench /usr/local/bin/bench
|
||||
RUN chmod +x /usr/local/bin/bench
|
||||
COPY common/worker/healthcheck.sh /usr/local/bin/healthcheck.sh
|
||||
RUN chmod +x /usr/local/bin/healthcheck.sh
|
||||
|
||||
# Setup docker-entrypoint
|
||||
COPY build/common/worker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
COPY common/worker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN ln -s /usr/local/bin/docker-entrypoint.sh / # backwards compat
|
||||
|
||||
# Use sites volume as working directory
|
||||
64
build/frappe-worker-arm/common/commands/auto_migrate.py
Normal file
64
build/frappe-worker-arm/common/commands/auto_migrate.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import os
|
||||
import semantic_version
|
||||
import git
|
||||
|
||||
from migrate import migrate_sites
|
||||
from utils import (
|
||||
save_version_file,
|
||||
get_apps,
|
||||
get_container_versions,
|
||||
get_version_file,
|
||||
get_config
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
is_ready = False
|
||||
apps = get_apps()
|
||||
|
||||
container_versions = get_container_versions(apps)
|
||||
|
||||
version_file = get_version_file()
|
||||
|
||||
if not version_file:
|
||||
version_file = container_versions
|
||||
save_version_file(version_file)
|
||||
|
||||
for app in apps:
|
||||
container_version = None
|
||||
file_version = None
|
||||
version_file_hash = None
|
||||
container_hash = None
|
||||
|
||||
repo = git.Repo(os.path.join('..', 'apps', app))
|
||||
branch = repo.active_branch.name
|
||||
|
||||
if branch == 'develop':
|
||||
version_file_hash = version_file.get(app+'_git_hash')
|
||||
container_hash = container_versions.get(app+'_git_hash')
|
||||
if container_hash and version_file_hash:
|
||||
if container_hash != version_file_hash:
|
||||
is_ready = True
|
||||
break
|
||||
|
||||
if version_file.get(app):
|
||||
file_version = semantic_version.Version(version_file.get(app))
|
||||
|
||||
if container_versions.get(app):
|
||||
container_version = semantic_version.Version(container_versions.get(app))
|
||||
|
||||
if file_version and container_version:
|
||||
if container_version > file_version:
|
||||
is_ready = True
|
||||
break
|
||||
|
||||
config = get_config()
|
||||
|
||||
if is_ready and config.get('maintenance_mode') != 1:
|
||||
migrate_sites(maintenance_mode=True)
|
||||
version_file = container_versions
|
||||
save_version_file(version_file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
build/frappe-worker-arm/common/commands/background.py
Normal file
11
build/frappe-worker-arm/common/commands/background.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from frappe.utils.scheduler import start_scheduler
|
||||
|
||||
|
||||
def main():
|
||||
print("Starting background scheduler . . .")
|
||||
start_scheduler()
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
39
build/frappe-worker-arm/common/commands/backup.py
Normal file
39
build/frappe-worker-arm/common/commands/backup.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import os
|
||||
import frappe
|
||||
from frappe.utils.backups import scheduled_backup
|
||||
from frappe.utils import cint, get_sites, now
|
||||
|
||||
|
||||
def backup(sites, with_files=False):
|
||||
for site in sites:
|
||||
frappe.init(site)
|
||||
frappe.connect()
|
||||
odb = scheduled_backup(
|
||||
ignore_files=not with_files,
|
||||
backup_path_db=None,
|
||||
backup_path_files=None,
|
||||
backup_path_private_files=None,
|
||||
force=True
|
||||
)
|
||||
print("database backup taken -", odb.backup_path_db, "- on", now())
|
||||
if with_files:
|
||||
print("files backup taken -", odb.backup_path_files, "- on", now())
|
||||
print("private files backup taken -", odb.backup_path_private_files, "- on", now())
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
def main():
|
||||
installed_sites = ":".join(get_sites())
|
||||
sites = os.environ.get("SITES", installed_sites).split(":")
|
||||
with_files = cint(os.environ.get("WITH_FILES"))
|
||||
|
||||
backup(sites, with_files)
|
||||
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
130
build/frappe-worker-arm/common/commands/check_connection.py
Normal file
130
build/frappe-worker-arm/common/commands/check_connection.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import socket
|
||||
import time
|
||||
from six.moves.urllib.parse import urlparse
|
||||
from utils import get_config
|
||||
from constants import (
|
||||
REDIS_QUEUE_KEY,
|
||||
REDIS_CACHE_KEY,
|
||||
REDIS_SOCKETIO_KEY,
|
||||
DB_HOST_KEY,
|
||||
DB_PORT_KEY,
|
||||
DB_PORT
|
||||
)
|
||||
|
||||
|
||||
def is_open(ip, port, timeout=30):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(timeout)
|
||||
try:
|
||||
s.connect((ip, int(port)))
|
||||
s.shutdown(socket.SHUT_RDWR)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def check_host(ip, port, retry=10, delay=3, print_attempt=True):
|
||||
ipup = False
|
||||
for i in range(retry):
|
||||
if print_attempt:
|
||||
print("Attempt {i} to connect to {ip}:{port}".format(ip=ip, port=port, i=i+1))
|
||||
if is_open(ip, port):
|
||||
ipup = True
|
||||
break
|
||||
else:
|
||||
time.sleep(delay)
|
||||
return ipup
|
||||
|
||||
|
||||
# Check service
|
||||
def check_service(
|
||||
retry=10,
|
||||
delay=3,
|
||||
print_attempt=True,
|
||||
service_name=None,
|
||||
service_port=None):
|
||||
|
||||
config = get_config()
|
||||
if not service_name:
|
||||
service_name = config.get(DB_HOST_KEY, 'mariadb')
|
||||
if not service_port:
|
||||
service_port = config.get(DB_PORT_KEY, DB_PORT)
|
||||
|
||||
is_db_connected = False
|
||||
is_db_connected = check_host(
|
||||
service_name,
|
||||
service_port,
|
||||
retry,
|
||||
delay,
|
||||
print_attempt)
|
||||
if not is_db_connected:
|
||||
print("Connection to {service_name}:{service_port} timed out".format(
|
||||
service_name=service_name,
|
||||
service_port=service_port,
|
||||
))
|
||||
exit(1)
|
||||
|
||||
|
||||
# Check redis queue
|
||||
def check_redis_queue(retry=10, delay=3, print_attempt=True):
|
||||
check_redis_queue = False
|
||||
config = get_config()
|
||||
redis_queue_url = urlparse(config.get(REDIS_QUEUE_KEY, "redis://redis-queue:6379")).netloc
|
||||
redis_queue, redis_queue_port = redis_queue_url.split(":")
|
||||
check_redis_queue = check_host(
|
||||
redis_queue,
|
||||
redis_queue_port,
|
||||
retry,
|
||||
delay,
|
||||
print_attempt)
|
||||
if not check_redis_queue:
|
||||
print("Connection to redis queue timed out")
|
||||
exit(1)
|
||||
|
||||
|
||||
# Check redis cache
|
||||
def check_redis_cache(retry=10, delay=3, print_attempt=True):
|
||||
check_redis_cache = False
|
||||
config = get_config()
|
||||
redis_cache_url = urlparse(config.get(REDIS_CACHE_KEY, "redis://redis-cache:6379")).netloc
|
||||
redis_cache, redis_cache_port = redis_cache_url.split(":")
|
||||
check_redis_cache = check_host(
|
||||
redis_cache,
|
||||
redis_cache_port,
|
||||
retry,
|
||||
delay,
|
||||
print_attempt)
|
||||
if not check_redis_cache:
|
||||
print("Connection to redis cache timed out")
|
||||
exit(1)
|
||||
|
||||
|
||||
# Check redis socketio
|
||||
def check_redis_socketio(retry=10, delay=3, print_attempt=True):
|
||||
check_redis_socketio = False
|
||||
config = get_config()
|
||||
redis_socketio_url = urlparse(config.get(REDIS_SOCKETIO_KEY, "redis://redis-socketio:6379")).netloc
|
||||
redis_socketio, redis_socketio_port = redis_socketio_url.split(":")
|
||||
check_redis_socketio = check_host(
|
||||
redis_socketio,
|
||||
redis_socketio_port,
|
||||
retry,
|
||||
delay,
|
||||
print_attempt)
|
||||
if not check_redis_socketio:
|
||||
print("Connection to redis socketio timed out")
|
||||
exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
check_service()
|
||||
check_redis_queue()
|
||||
check_redis_cache()
|
||||
check_redis_socketio()
|
||||
print('Connections OK')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
32
build/frappe-worker-arm/common/commands/console.py
Normal file
32
build/frappe-worker-arm/common/commands/console.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import sys
|
||||
import frappe
|
||||
import IPython
|
||||
|
||||
from frappe.utils import get_sites
|
||||
|
||||
|
||||
def console(site):
|
||||
"Start ipython console for a site"
|
||||
if site not in get_sites():
|
||||
print("Site {0} does not exist on the current bench".format(site))
|
||||
return
|
||||
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.local.lang = frappe.db.get_default("lang")
|
||||
all_apps = frappe.get_installed_apps()
|
||||
for app in all_apps:
|
||||
locals()[app] = __import__(app)
|
||||
print("Apps in this namespace:\n{}".format(", ".join(all_apps)))
|
||||
IPython.embed(display_banner="", header="")
|
||||
|
||||
|
||||
def main():
|
||||
site = sys.argv[-1]
|
||||
console(site)
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
13
build/frappe-worker-arm/common/commands/constants.py
Normal file
13
build/frappe-worker-arm/common/commands/constants.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
REDIS_QUEUE_KEY = 'redis_queue'
|
||||
REDIS_CACHE_KEY = 'redis_cache'
|
||||
REDIS_SOCKETIO_KEY = 'redis_socketio'
|
||||
DB_HOST_KEY = 'db_host'
|
||||
DB_PORT_KEY = 'db_port'
|
||||
DB_PORT = 3306
|
||||
APP_VERSIONS_JSON_FILE = 'app_versions.json'
|
||||
APPS_TXT_FILE = 'apps.txt'
|
||||
COMMON_SITE_CONFIG_FILE = 'common_site_config.json'
|
||||
DATE_FORMAT = "%Y%m%d_%H%M%S"
|
||||
RDS_DB = 'rds_db'
|
||||
RDS_PRIVILEGES = "SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE, LOCK TABLES"
|
||||
ARCHIVE_SITES_PATH = '/home/frappe/frappe-bench/sites/archive_sites'
|
||||
61
build/frappe-worker-arm/common/commands/doctor.py
Normal file
61
build/frappe-worker-arm/common/commands/doctor.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import argparse
|
||||
|
||||
from check_connection import (
|
||||
check_service,
|
||||
check_redis_cache,
|
||||
check_redis_queue,
|
||||
check_redis_socketio,
|
||||
)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-p',
|
||||
'--ping-service',
|
||||
dest='ping_services',
|
||||
action='append',
|
||||
type=str,
|
||||
help='list of services to ping, e.g. doctor -p "postgres:5432" --ping-service "mariadb:3306"',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
check_service(retry=1, delay=0, print_attempt=False)
|
||||
print("Bench database Connected")
|
||||
check_redis_cache(retry=1, delay=0, print_attempt=False)
|
||||
print("Redis Cache Connected")
|
||||
check_redis_queue(retry=1, delay=0, print_attempt=False)
|
||||
print("Redis Queue Connected")
|
||||
check_redis_socketio(retry=1, delay=0, print_attempt=False)
|
||||
print("Redis SocketIO Connected")
|
||||
|
||||
if(args.ping_services):
|
||||
for service in args.ping_services:
|
||||
service_name = None
|
||||
service_port = None
|
||||
|
||||
try:
|
||||
service_name, service_port = service.split(':')
|
||||
except ValueError:
|
||||
print('Service should be in format host:port, e.g postgres:5432')
|
||||
exit(1)
|
||||
|
||||
check_service(
|
||||
retry=1,
|
||||
delay=0,
|
||||
print_attempt=False,
|
||||
service_name=service_name,
|
||||
service_port=service_port,
|
||||
)
|
||||
print("{0}:{1} Connected".format(service_name, service_port))
|
||||
|
||||
print("Health check successful")
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
39
build/frappe-worker-arm/common/commands/drop.py
Normal file
39
build/frappe-worker-arm/common/commands/drop.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import os
|
||||
import frappe
|
||||
|
||||
from frappe.commands.site import _drop_site
|
||||
from constants import ARCHIVE_SITES_PATH
|
||||
from utils import get_password
|
||||
|
||||
|
||||
def main():
|
||||
site_name = os.environ.get("SITE_NAME", 'site1.localhost')
|
||||
db_root_username = os.environ.get("DB_ROOT_USER", 'root')
|
||||
mariadb_root_password = get_password("MYSQL_ROOT_PASSWORD", 'admin')
|
||||
postgres_root_password = get_password("POSTGRES_PASSWORD")
|
||||
db_root_password = mariadb_root_password
|
||||
|
||||
if postgres_root_password:
|
||||
db_root_password = postgres_root_password
|
||||
|
||||
force = True if os.environ.get("FORCE", None) else False
|
||||
no_backup = True if os.environ.get("NO_BACKUP", None) else False
|
||||
frappe.init(site_name, new_site=True)
|
||||
|
||||
_drop_site(
|
||||
site=site_name,
|
||||
root_login=db_root_username,
|
||||
root_password=db_root_password,
|
||||
archived_sites_path=ARCHIVE_SITES_PATH,
|
||||
force=force,
|
||||
no_backup=no_backup
|
||||
)
|
||||
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
51
build/frappe-worker-arm/common/commands/migrate.py
Normal file
51
build/frappe-worker-arm/common/commands/migrate.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import os
|
||||
import frappe
|
||||
|
||||
from frappe.utils import cint, get_sites
|
||||
from utils import get_config, save_config
|
||||
|
||||
|
||||
def set_maintenance_mode(enable=True):
|
||||
conf = get_config()
|
||||
|
||||
if enable:
|
||||
conf.update({"maintenance_mode": 1, "pause_scheduler": 1})
|
||||
save_config(conf)
|
||||
|
||||
if not enable:
|
||||
conf.update({"maintenance_mode": 0, "pause_scheduler": 0})
|
||||
save_config(conf)
|
||||
|
||||
|
||||
def migrate_sites(maintenance_mode=False):
|
||||
installed_sites = ":".join(get_sites())
|
||||
sites = os.environ.get("SITES", installed_sites).split(":")
|
||||
if not maintenance_mode:
|
||||
maintenance_mode = cint(os.environ.get("MAINTENANCE_MODE"))
|
||||
|
||||
if maintenance_mode:
|
||||
set_maintenance_mode(True)
|
||||
|
||||
for site in sites:
|
||||
print('Migrating', site)
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
try:
|
||||
from frappe.migrate import migrate
|
||||
migrate()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
# Disable maintenance mode after migration
|
||||
set_maintenance_mode(False)
|
||||
|
||||
|
||||
def main():
|
||||
migrate_sites()
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
109
build/frappe-worker-arm/common/commands/new.py
Normal file
109
build/frappe-worker-arm/common/commands/new.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import os
|
||||
import frappe
|
||||
import semantic_version
|
||||
|
||||
from frappe.commands.site import _new_site
|
||||
from frappe.installer import update_site_config
|
||||
from constants import COMMON_SITE_CONFIG_FILE, RDS_DB, RDS_PRIVILEGES
|
||||
from utils import (
|
||||
run_command,
|
||||
get_config,
|
||||
get_site_config,
|
||||
get_password,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
config = get_config()
|
||||
db_type = 'mariadb'
|
||||
db_port = config.get('db_port', 3306)
|
||||
db_host = config.get('db_host')
|
||||
site_name = os.environ.get("SITE_NAME", 'site1.localhost')
|
||||
db_root_username = os.environ.get("DB_ROOT_USER", 'root')
|
||||
mariadb_root_password = get_password("MYSQL_ROOT_PASSWORD", 'admin')
|
||||
postgres_root_password = get_password("POSTGRES_PASSWORD")
|
||||
db_root_password = mariadb_root_password
|
||||
|
||||
if postgres_root_password:
|
||||
db_type = 'postgres'
|
||||
db_host = os.environ.get("POSTGRES_HOST")
|
||||
db_port = 5432
|
||||
db_root_password = postgres_root_password
|
||||
if not db_host:
|
||||
db_host = config.get('db_host')
|
||||
print('Environment variable POSTGRES_HOST not found.')
|
||||
print('Using db_host from common_site_config.json')
|
||||
|
||||
sites_path = os.getcwd()
|
||||
common_site_config_path = os.path.join(sites_path, COMMON_SITE_CONFIG_FILE)
|
||||
update_site_config("root_login", db_root_username, validate = False, site_config_path = common_site_config_path)
|
||||
update_site_config("root_password", db_root_password, validate = False, site_config_path = common_site_config_path)
|
||||
|
||||
force = True if os.environ.get("FORCE", None) else False
|
||||
install_apps = os.environ.get("INSTALL_APPS", None)
|
||||
install_apps = install_apps.split(',') if install_apps else []
|
||||
frappe.init(site_name, new_site=True)
|
||||
|
||||
if semantic_version.Version(frappe.__version__).major > 11:
|
||||
_new_site(
|
||||
None,
|
||||
site_name,
|
||||
mariadb_root_username=db_root_username,
|
||||
mariadb_root_password=db_root_password,
|
||||
admin_password=get_password("ADMIN_PASSWORD", 'admin'),
|
||||
verbose=True,
|
||||
install_apps=install_apps,
|
||||
source_sql=None,
|
||||
force=force,
|
||||
db_type=db_type,
|
||||
reinstall=False,
|
||||
db_host=db_host,
|
||||
db_port=db_port,
|
||||
)
|
||||
else:
|
||||
_new_site(
|
||||
None,
|
||||
site_name,
|
||||
mariadb_root_username=db_root_username,
|
||||
mariadb_root_password=db_root_password,
|
||||
admin_password=get_password("ADMIN_PASSWORD", 'admin'),
|
||||
verbose=True,
|
||||
install_apps=install_apps,
|
||||
source_sql=None,
|
||||
force=force,
|
||||
reinstall=False,
|
||||
)
|
||||
|
||||
|
||||
if db_type == "mariadb":
|
||||
site_config = get_site_config(site_name)
|
||||
db_name = site_config.get('db_name')
|
||||
db_password = site_config.get('db_password')
|
||||
|
||||
mysql_command = ["mysql", f"-h{db_host}", f"-u{db_root_username}", f"-p{mariadb_root_password}", "-e"]
|
||||
|
||||
# Drop User if exists
|
||||
command = mysql_command + [f"DROP USER IF EXISTS '{db_name}'; FLUSH PRIVILEGES;"]
|
||||
run_command(command)
|
||||
|
||||
# Grant permission to database and set password
|
||||
grant_privileges = "ALL PRIVILEGES"
|
||||
|
||||
# for Amazon RDS
|
||||
if config.get(RDS_DB) or site_config.get(RDS_DB):
|
||||
grant_privileges = RDS_PRIVILEGES
|
||||
|
||||
command = mysql_command + [f"\
|
||||
CREATE USER IF NOT EXISTS '{db_name}'@'%' IDENTIFIED BY '{db_password}'; \
|
||||
GRANT {grant_privileges} ON `{db_name}`.* TO '{db_name}'@'%'; \
|
||||
FLUSH PRIVILEGES;"]
|
||||
run_command(command)
|
||||
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
156
build/frappe-worker-arm/common/commands/push_backup.py
Normal file
156
build/frappe-worker-arm/common/commands/push_backup.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import os
|
||||
import time
|
||||
import boto3
|
||||
|
||||
import datetime
|
||||
from glob import glob
|
||||
from frappe.utils import get_sites
|
||||
from constants import DATE_FORMAT
|
||||
from utils import (
|
||||
get_s3_config,
|
||||
upload_file_to_s3,
|
||||
check_s3_environment_variables,
|
||||
)
|
||||
|
||||
|
||||
def get_file_ext():
|
||||
return {
|
||||
"database": "-database.sql.gz",
|
||||
"private_files": "-private-files.tar",
|
||||
"public_files": "-files.tar",
|
||||
"site_config": "-site_config_backup.json"
|
||||
}
|
||||
|
||||
|
||||
def get_backup_details(sitename):
|
||||
backup_details = dict()
|
||||
file_ext = get_file_ext()
|
||||
|
||||
# add trailing slash https://stackoverflow.com/a/15010678
|
||||
site_backup_path = os.path.join(os.getcwd(), sitename, "private", "backups", "")
|
||||
|
||||
if os.path.exists(site_backup_path):
|
||||
for filetype, ext in file_ext.items():
|
||||
site_slug = sitename.replace('.', '_')
|
||||
pattern = site_backup_path + '*-' + site_slug + ext
|
||||
backup_files = list(filter(os.path.isfile, glob(pattern)))
|
||||
|
||||
if len(backup_files) > 0:
|
||||
backup_files.sort(key=lambda file: os.stat(os.path.join(site_backup_path, file)).st_ctime)
|
||||
backup_date = datetime.datetime.strptime(time.ctime(os.path.getmtime(backup_files[0])), "%a %b %d %H:%M:%S %Y")
|
||||
backup_details[filetype] = {
|
||||
"sitename": sitename,
|
||||
"file_size_in_bytes": os.stat(backup_files[-1]).st_size,
|
||||
"file_path": os.path.abspath(backup_files[-1]),
|
||||
"filename": os.path.basename(backup_files[-1]),
|
||||
"backup_date": backup_date.date().strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
return backup_details
|
||||
|
||||
|
||||
def delete_old_backups(limit, bucket, site_name):
|
||||
all_backups = list()
|
||||
all_backup_dates = list()
|
||||
backup_limit = int(limit)
|
||||
check_s3_environment_variables()
|
||||
bucket_dir = os.environ.get('BUCKET_DIR')
|
||||
oldest_backup_date = None
|
||||
|
||||
s3 = boto3.resource(
|
||||
's3',
|
||||
region_name=os.environ.get('REGION'),
|
||||
aws_access_key_id=os.environ.get('ACCESS_KEY_ID'),
|
||||
aws_secret_access_key=os.environ.get('SECRET_ACCESS_KEY'),
|
||||
endpoint_url=os.environ.get('ENDPOINT_URL')
|
||||
)
|
||||
|
||||
bucket = s3.Bucket(bucket)
|
||||
objects = bucket.meta.client.list_objects_v2(
|
||||
Bucket=bucket.name,
|
||||
Delimiter='/')
|
||||
|
||||
if objects:
|
||||
for obj in objects.get('CommonPrefixes'):
|
||||
if obj.get('Prefix') == bucket_dir + '/':
|
||||
for backup_obj in bucket.objects.filter(Prefix=obj.get('Prefix')):
|
||||
if backup_obj.get()["ContentType"] == "application/x-directory":
|
||||
continue
|
||||
try:
|
||||
# backup_obj.key is bucket_dir/site/date_time/backupfile.extension
|
||||
bucket_dir, site_slug, date_time, backupfile = backup_obj.key.split('/')
|
||||
date_time_object = datetime.datetime.strptime(
|
||||
date_time, DATE_FORMAT
|
||||
)
|
||||
|
||||
if site_name in backup_obj.key:
|
||||
all_backup_dates.append(date_time_object)
|
||||
all_backups.append(backup_obj.key)
|
||||
except IndexError as error:
|
||||
print(error)
|
||||
exit(1)
|
||||
|
||||
if len(all_backup_dates) > 0:
|
||||
oldest_backup_date = min(all_backup_dates)
|
||||
|
||||
if len(all_backups) / 3 > backup_limit:
|
||||
oldest_backup = None
|
||||
for backup in all_backups:
|
||||
try:
|
||||
# backup is bucket_dir/site/date_time/backupfile.extension
|
||||
backup_dir, site_slug, backup_dt_string, filename = backup.split('/')
|
||||
backup_datetime = datetime.datetime.strptime(
|
||||
backup_dt_string, DATE_FORMAT
|
||||
)
|
||||
if backup_datetime == oldest_backup_date:
|
||||
oldest_backup = backup
|
||||
|
||||
except IndexError as error:
|
||||
print(error)
|
||||
exit(1)
|
||||
|
||||
if oldest_backup:
|
||||
for obj in bucket.objects.filter(Prefix=oldest_backup):
|
||||
# delete all keys that are inside the oldest_backup
|
||||
if bucket_dir in obj.key:
|
||||
print('Deleteing ' + obj.key)
|
||||
s3.Object(bucket.name, obj.key).delete()
|
||||
|
||||
|
||||
def main():
|
||||
details = dict()
|
||||
sites = get_sites()
|
||||
conn, bucket = get_s3_config()
|
||||
|
||||
for site in sites:
|
||||
details = get_backup_details(site)
|
||||
db_file = details.get('database', {}).get('file_path')
|
||||
folder = os.environ.get('BUCKET_DIR') + '/' + site + '/'
|
||||
if db_file:
|
||||
folder = os.environ.get('BUCKET_DIR') + '/' + site + '/' + os.path.basename(db_file)[:15] + '/'
|
||||
upload_file_to_s3(db_file, folder, conn, bucket)
|
||||
|
||||
# Archive site_config.json
|
||||
site_config_file = details.get('site_config', {}).get('file_path')
|
||||
if not site_config_file:
|
||||
site_config_file = os.path.join(os.getcwd(), site, 'site_config.json')
|
||||
upload_file_to_s3(site_config_file, folder, conn, bucket)
|
||||
|
||||
public_files = details.get('public_files', {}).get('file_path')
|
||||
if public_files:
|
||||
folder = os.environ.get('BUCKET_DIR') + '/' + site + '/' + os.path.basename(public_files)[:15] + '/'
|
||||
upload_file_to_s3(public_files, folder, conn, bucket)
|
||||
|
||||
private_files = details.get('private_files', {}).get('file_path')
|
||||
if private_files:
|
||||
folder = os.environ.get('BUCKET_DIR') + '/' + site + '/' + os.path.basename(private_files)[:15] + '/'
|
||||
upload_file_to_s3(private_files, folder, conn, bucket)
|
||||
|
||||
delete_old_backups(os.environ.get('BACKUP_LIMIT', '3'), bucket, site)
|
||||
|
||||
print('push-backup complete')
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
294
build/frappe-worker-arm/common/commands/restore_backup.py
Normal file
294
build/frappe-worker-arm/common/commands/restore_backup.py
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import os
|
||||
import datetime
|
||||
import tarfile
|
||||
import hashlib
|
||||
import frappe
|
||||
import boto3
|
||||
|
||||
from frappe.utils import get_sites, random_string
|
||||
from frappe.installer import (
|
||||
make_conf,
|
||||
get_conf_params,
|
||||
make_site_dirs,
|
||||
update_site_config
|
||||
)
|
||||
from constants import COMMON_SITE_CONFIG_FILE, DATE_FORMAT, RDS_DB, RDS_PRIVILEGES
|
||||
from utils import (
|
||||
run_command,
|
||||
list_directories,
|
||||
set_key_in_site_config,
|
||||
get_site_config,
|
||||
get_config,
|
||||
get_password,
|
||||
check_s3_environment_variables,
|
||||
)
|
||||
|
||||
|
||||
def get_backup_dir():
|
||||
return os.path.join(
|
||||
os.path.expanduser('~'),
|
||||
'backups'
|
||||
)
|
||||
|
||||
|
||||
def decompress_db(database_file, site):
|
||||
command = ["gunzip", "-c", database_file]
|
||||
with open(database_file.replace(".gz", ""), "w") as db_file:
|
||||
print('Extract Database GZip for site {}'.format(site))
|
||||
run_command(command, stdout=db_file)
|
||||
|
||||
|
||||
def restore_database(files_base, site_config_path, site):
|
||||
# restore database
|
||||
database_file = files_base + '-database.sql.gz'
|
||||
decompress_db(database_file, site)
|
||||
config = get_config()
|
||||
|
||||
# Set db_type if it exists in backup site_config.json
|
||||
set_key_in_site_config('db_type', site, site_config_path)
|
||||
# Set db_host if it exists in backup site_config.json
|
||||
set_key_in_site_config('db_host', site, site_config_path)
|
||||
# Set db_port if it exists in backup site_config.json
|
||||
set_key_in_site_config('db_port', site, site_config_path)
|
||||
|
||||
# get updated site_config
|
||||
site_config = get_site_config(site)
|
||||
|
||||
# if no db_type exists, default to mariadb
|
||||
db_type = site_config.get('db_type', 'mariadb')
|
||||
is_database_restored = False
|
||||
|
||||
if db_type == 'mariadb':
|
||||
restore_mariadb(
|
||||
config=config,
|
||||
site_config=site_config,
|
||||
database_file=database_file)
|
||||
is_database_restored = True
|
||||
elif db_type == 'postgres':
|
||||
restore_postgres(
|
||||
config=config,
|
||||
site_config=site_config,
|
||||
database_file=database_file)
|
||||
is_database_restored = True
|
||||
|
||||
if is_database_restored:
|
||||
# Set encryption_key if it exists in backup site_config.json
|
||||
set_key_in_site_config('encryption_key', site, site_config_path)
|
||||
|
||||
|
||||
def restore_files(files_base):
|
||||
public_files = files_base + '-files.tar'
|
||||
# extract tar
|
||||
public_tar = tarfile.open(public_files)
|
||||
print('Extracting {}'.format(public_files))
|
||||
public_tar.extractall()
|
||||
|
||||
|
||||
def restore_private_files(files_base):
|
||||
private_files = files_base + '-private-files.tar'
|
||||
private_tar = tarfile.open(private_files)
|
||||
print('Extracting {}'.format(private_files))
|
||||
private_tar.extractall()
|
||||
|
||||
|
||||
def pull_backup_from_s3():
|
||||
check_s3_environment_variables()
|
||||
|
||||
# https://stackoverflow.com/a/54672690
|
||||
s3 = boto3.resource(
|
||||
's3',
|
||||
region_name=os.environ.get('REGION'),
|
||||
aws_access_key_id=os.environ.get('ACCESS_KEY_ID'),
|
||||
aws_secret_access_key=os.environ.get('SECRET_ACCESS_KEY'),
|
||||
endpoint_url=os.environ.get('ENDPOINT_URL')
|
||||
)
|
||||
|
||||
bucket_dir = os.environ.get('BUCKET_DIR')
|
||||
bucket_name = os.environ.get('BUCKET_NAME')
|
||||
bucket = s3.Bucket(bucket_name)
|
||||
|
||||
# Change directory to /home/frappe/backups
|
||||
os.chdir(get_backup_dir())
|
||||
|
||||
backup_files = []
|
||||
sites = set()
|
||||
site_timestamps = set()
|
||||
download_backups = []
|
||||
|
||||
for obj in bucket.objects.filter(Prefix=bucket_dir):
|
||||
if obj.get()["ContentType"] == "application/x-directory":
|
||||
continue
|
||||
backup_file = obj.key.replace(os.path.join(bucket_dir, ''), '')
|
||||
backup_files.append(backup_file)
|
||||
site_name, timestamp, backup_type = backup_file.split('/')
|
||||
site_timestamp = site_name + '/' + timestamp
|
||||
sites.add(site_name)
|
||||
site_timestamps.add(site_timestamp)
|
||||
|
||||
# sort sites for latest backups
|
||||
for site in sites:
|
||||
backup_timestamps = []
|
||||
for site_timestamp in site_timestamps:
|
||||
site_name, timestamp = site_timestamp.split('/')
|
||||
if site == site_name:
|
||||
timestamp_datetime = datetime.datetime.strptime(
|
||||
timestamp, DATE_FORMAT
|
||||
)
|
||||
backup_timestamps.append(timestamp)
|
||||
download_backups.append(site + '/' + max(backup_timestamps))
|
||||
|
||||
# Only download latest backups
|
||||
for backup_file in backup_files:
|
||||
for backup in download_backups:
|
||||
if backup in backup_file:
|
||||
if not os.path.exists(os.path.dirname(backup_file)):
|
||||
os.makedirs(os.path.dirname(backup_file))
|
||||
print('Downloading {}'.format(backup_file))
|
||||
bucket.download_file(bucket_dir + '/' + backup_file, backup_file)
|
||||
|
||||
os.chdir(os.path.join(os.path.expanduser('~'), 'frappe-bench', 'sites'))
|
||||
|
||||
|
||||
def restore_postgres(config, site_config, database_file):
|
||||
# common config
|
||||
common_site_config_path = os.path.join(os.getcwd(), COMMON_SITE_CONFIG_FILE)
|
||||
|
||||
db_root_user = config.get('root_login')
|
||||
if not db_root_user:
|
||||
postgres_user = os.environ.get('DB_ROOT_USER')
|
||||
if not postgres_user:
|
||||
print('Variable DB_ROOT_USER not set')
|
||||
exit(1)
|
||||
|
||||
db_root_user = postgres_user
|
||||
update_site_config(
|
||||
"root_login",
|
||||
db_root_user,
|
||||
validate=False,
|
||||
site_config_path=common_site_config_path)
|
||||
|
||||
db_root_password = config.get('root_password')
|
||||
if not db_root_password:
|
||||
root_password = get_password('POSTGRES_PASSWORD')
|
||||
if not root_password:
|
||||
print('Variable POSTGRES_PASSWORD not set')
|
||||
exit(1)
|
||||
|
||||
db_root_password = root_password
|
||||
update_site_config(
|
||||
"root_password",
|
||||
db_root_password,
|
||||
validate=False,
|
||||
site_config_path=common_site_config_path)
|
||||
|
||||
# site config
|
||||
db_host = site_config.get('db_host')
|
||||
db_port = site_config.get('db_port', 5432)
|
||||
db_name = site_config.get('db_name')
|
||||
db_password = site_config.get('db_password')
|
||||
|
||||
psql_command = ["psql"]
|
||||
psql_uri = f"postgres://{db_root_user}:{db_root_password}@{db_host}:{db_port}"
|
||||
|
||||
print('Restoring PostgreSQL')
|
||||
run_command(psql_command + [psql_uri, "-c", f"DROP DATABASE IF EXISTS \"{db_name}\""])
|
||||
run_command(psql_command + [psql_uri, "-c", f"DROP USER IF EXISTS {db_name}"])
|
||||
run_command(psql_command + [psql_uri, "-c", f"CREATE DATABASE \"{db_name}\""])
|
||||
run_command(psql_command + [psql_uri, "-c", f"CREATE user {db_name} password '{db_password}'"])
|
||||
run_command(psql_command + [psql_uri, "-c", f"GRANT ALL PRIVILEGES ON DATABASE \"{db_name}\" TO {db_name}"])
|
||||
with open(database_file.replace('.gz', ''), 'r') as db_file:
|
||||
run_command(psql_command + [f"{psql_uri}/{db_name}", "<"], stdin=db_file)
|
||||
|
||||
|
||||
def restore_mariadb(config, site_config, database_file):
|
||||
db_root_password = get_password('MYSQL_ROOT_PASSWORD')
|
||||
if not db_root_password:
|
||||
print('Variable MYSQL_ROOT_PASSWORD not set')
|
||||
exit(1)
|
||||
|
||||
db_root_user = os.environ.get("DB_ROOT_USER", 'root')
|
||||
|
||||
db_host = site_config.get('db_host', config.get('db_host'))
|
||||
db_port = site_config.get('db_port', config.get('db_port', 3306))
|
||||
db_name = site_config.get('db_name')
|
||||
db_password = site_config.get('db_password')
|
||||
|
||||
# mysql command prefix
|
||||
mysql_command = ["mysql", f"-u{db_root_user}", f"-h{db_host}", f"-p{db_root_password}", f"-P{db_port}"]
|
||||
|
||||
# drop db if exists for clean restore
|
||||
drop_database = mysql_command + ["-e", f"DROP DATABASE IF EXISTS `{db_name}`;"]
|
||||
run_command(drop_database)
|
||||
|
||||
# create db
|
||||
create_database = mysql_command + ["-e", f"CREATE DATABASE IF NOT EXISTS `{db_name}`;"]
|
||||
run_command(create_database)
|
||||
|
||||
# create user
|
||||
create_user = mysql_command + ["-e", f"CREATE USER IF NOT EXISTS '{db_name}'@'%' IDENTIFIED BY '{db_password}'; FLUSH PRIVILEGES;"]
|
||||
run_command(create_user)
|
||||
|
||||
# grant db privileges to user
|
||||
|
||||
grant_privileges = "ALL PRIVILEGES"
|
||||
|
||||
# for Amazon RDS
|
||||
if config.get(RDS_DB) or site_config.get(RDS_DB):
|
||||
grant_privileges = RDS_PRIVILEGES
|
||||
|
||||
grant_privileges_command = mysql_command + ["-e", f"GRANT {grant_privileges} ON `{db_name}`.* TO '{db_name}'@'%' IDENTIFIED BY '{db_password}'; FLUSH PRIVILEGES;"]
|
||||
run_command(grant_privileges_command)
|
||||
|
||||
print('Restoring MariaDB')
|
||||
with open(database_file.replace('.gz', ''), 'r') as db_file:
|
||||
run_command(mysql_command + [f"{db_name}"], stdin=db_file)
|
||||
|
||||
|
||||
def main():
|
||||
backup_dir = get_backup_dir()
|
||||
|
||||
if len(list_directories(backup_dir)) == 0:
|
||||
pull_backup_from_s3()
|
||||
|
||||
for site in list_directories(backup_dir):
|
||||
site_slug = site.replace('.', '_')
|
||||
backups = [datetime.datetime.strptime(backup, DATE_FORMAT) for backup in list_directories(os.path.join(backup_dir, site))]
|
||||
latest_backup = max(backups).strftime(DATE_FORMAT)
|
||||
files_base = os.path.join(backup_dir, site, latest_backup, '')
|
||||
files_base += latest_backup + '-' + site_slug
|
||||
site_config_path = files_base + '-site_config_backup.json'
|
||||
if not os.path.exists(site_config_path):
|
||||
site_config_path = os.path.join(backup_dir, site, 'site_config.json')
|
||||
if site in get_sites():
|
||||
print('Overwrite site {}'.format(site))
|
||||
restore_database(files_base, site_config_path, site)
|
||||
restore_private_files(files_base)
|
||||
restore_files(files_base)
|
||||
else:
|
||||
site_config = get_conf_params(
|
||||
db_name='_' + hashlib.sha1(site.encode()).hexdigest()[:16],
|
||||
db_password=random_string(16)
|
||||
)
|
||||
|
||||
frappe.local.site = site
|
||||
frappe.local.sites_path = os.getcwd()
|
||||
frappe.local.site_path = os.getcwd() + '/' + site
|
||||
make_conf(
|
||||
db_name=site_config.get('db_name'),
|
||||
db_password=site_config.get('db_password'),
|
||||
)
|
||||
make_site_dirs()
|
||||
|
||||
print('Create site {}'.format(site))
|
||||
restore_database(files_base, site_config_path, site)
|
||||
restore_private_files(files_base)
|
||||
restore_files(files_base)
|
||||
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
204
build/frappe-worker-arm/common/commands/utils.py
Normal file
204
build/frappe-worker-arm/common/commands/utils.py
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import boto3
|
||||
import git
|
||||
|
||||
from frappe.installer import update_site_config
|
||||
from constants import (
|
||||
APP_VERSIONS_JSON_FILE,
|
||||
APPS_TXT_FILE,
|
||||
COMMON_SITE_CONFIG_FILE
|
||||
)
|
||||
|
||||
def run_command(command, stdout=None, stdin=None, stderr=None):
|
||||
stdout = stdout or subprocess.PIPE
|
||||
stderr = stderr or subprocess.PIPE
|
||||
stdin = stdin or subprocess.PIPE
|
||||
process = subprocess.Popen(command, stdout=stdout, stdin=stdin, stderr=stderr)
|
||||
out, error = process.communicate()
|
||||
if process.returncode:
|
||||
print("Something went wrong:")
|
||||
print(f"return code: {process.returncode}")
|
||||
print(f"stdout:\n{out}")
|
||||
print(f"\nstderr:\n{error}")
|
||||
exit(process.returncode)
|
||||
|
||||
|
||||
def save_version_file(versions):
|
||||
with open(APP_VERSIONS_JSON_FILE, 'w') as f:
|
||||
return json.dump(versions, f, indent=1, sort_keys=True)
|
||||
|
||||
|
||||
def get_apps():
|
||||
apps = []
|
||||
try:
|
||||
with open(APPS_TXT_FILE) as apps_file:
|
||||
for app in apps_file.readlines():
|
||||
if app.strip():
|
||||
apps.append(app.strip())
|
||||
|
||||
except FileNotFoundError as exception:
|
||||
print(exception)
|
||||
exit(1)
|
||||
except Exception:
|
||||
print(APPS_TXT_FILE + " is not valid")
|
||||
exit(1)
|
||||
|
||||
return apps
|
||||
|
||||
|
||||
def get_container_versions(apps):
|
||||
versions = {}
|
||||
for app in apps:
|
||||
try:
|
||||
version = __import__(app).__version__
|
||||
versions.update({app: version})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
path = os.path.join('..', 'apps', app)
|
||||
repo = git.Repo(path)
|
||||
commit_hash = repo.head.object.hexsha
|
||||
versions.update({app+'_git_hash': commit_hash})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return versions
|
||||
|
||||
|
||||
def get_version_file():
|
||||
versions = None
|
||||
try:
|
||||
with open(APP_VERSIONS_JSON_FILE) as versions_file:
|
||||
versions = json.load(versions_file)
|
||||
except Exception:
|
||||
pass
|
||||
return versions
|
||||
|
||||
|
||||
def get_config():
|
||||
config = None
|
||||
try:
|
||||
with open(COMMON_SITE_CONFIG_FILE) as config_file:
|
||||
config = json.load(config_file)
|
||||
except FileNotFoundError as exception:
|
||||
print(exception)
|
||||
exit(1)
|
||||
except Exception:
|
||||
print(COMMON_SITE_CONFIG_FILE + " is not valid")
|
||||
exit(1)
|
||||
return config
|
||||
|
||||
|
||||
def get_site_config(site_name):
|
||||
site_config = None
|
||||
with open('{site_name}/site_config.json'.format(site_name=site_name)) as site_config_file:
|
||||
site_config = json.load(site_config_file)
|
||||
return site_config
|
||||
|
||||
|
||||
def save_config(config):
|
||||
with open(COMMON_SITE_CONFIG_FILE, 'w') as f:
|
||||
return json.dump(config, f, indent=1, sort_keys=True)
|
||||
|
||||
|
||||
def get_password(env_var, default=None):
|
||||
return os.environ.get(env_var) or get_password_from_secret(f"{env_var}_FILE") or default
|
||||
|
||||
|
||||
def get_password_from_secret(env_var):
|
||||
"""Fetches the secret value from the docker secret file
|
||||
usually located inside /run/secrets/
|
||||
Arguments:
|
||||
env_var {str} -- Name of the environment variable
|
||||
containing the path to the secret file.
|
||||
Returns:
|
||||
[str] -- Secret value
|
||||
"""
|
||||
passwd = None
|
||||
secret_file_path = os.environ.get(env_var)
|
||||
if secret_file_path:
|
||||
with open(secret_file_path) as secret_file:
|
||||
passwd = secret_file.read().strip()
|
||||
|
||||
return passwd
|
||||
|
||||
|
||||
def get_s3_config():
|
||||
check_s3_environment_variables()
|
||||
bucket = os.environ.get('BUCKET_NAME')
|
||||
|
||||
conn = boto3.client(
|
||||
's3',
|
||||
region_name=os.environ.get('REGION'),
|
||||
aws_access_key_id=os.environ.get('ACCESS_KEY_ID'),
|
||||
aws_secret_access_key=os.environ.get('SECRET_ACCESS_KEY'),
|
||||
endpoint_url=os.environ.get('ENDPOINT_URL')
|
||||
)
|
||||
|
||||
return conn, bucket
|
||||
|
||||
|
||||
def upload_file_to_s3(filename, folder, conn, bucket):
|
||||
|
||||
destpath = os.path.join(folder, os.path.basename(filename))
|
||||
try:
|
||||
print("Uploading file:", filename)
|
||||
conn.upload_file(filename, bucket, destpath)
|
||||
|
||||
except Exception as e:
|
||||
print("Error uploading: %s" % (e))
|
||||
exit(1)
|
||||
|
||||
|
||||
def list_directories(path):
|
||||
directories = []
|
||||
for name in os.listdir(path):
|
||||
if os.path.isdir(os.path.join(path, name)):
|
||||
directories.append(name)
|
||||
return directories
|
||||
|
||||
|
||||
def get_site_config_from_path(site_config_path):
|
||||
site_config = dict()
|
||||
if os.path.exists(site_config_path):
|
||||
with open(site_config_path, 'r') as sc:
|
||||
site_config = json.load(sc)
|
||||
return site_config
|
||||
|
||||
|
||||
def set_key_in_site_config(key, site, site_config_path):
|
||||
site_config = get_site_config_from_path(site_config_path)
|
||||
value = site_config.get(key)
|
||||
if value:
|
||||
print('Set {key} in site config for site: {site}'.format(key=key, site=site))
|
||||
update_site_config(key, value,
|
||||
site_config_path=os.path.join(os.getcwd(), site, "site_config.json"))
|
||||
|
||||
|
||||
def check_s3_environment_variables():
|
||||
if 'BUCKET_NAME' not in os.environ:
|
||||
print('Variable BUCKET_NAME not set')
|
||||
exit(1)
|
||||
|
||||
if 'ACCESS_KEY_ID' not in os.environ:
|
||||
print('Variable ACCESS_KEY_ID not set')
|
||||
exit(1)
|
||||
|
||||
if 'SECRET_ACCESS_KEY' not in os.environ:
|
||||
print('Variable SECRET_ACCESS_KEY not set')
|
||||
exit(1)
|
||||
|
||||
if 'ENDPOINT_URL' not in os.environ:
|
||||
print('Variable ENDPOINT_URL not set')
|
||||
exit(1)
|
||||
|
||||
if 'BUCKET_DIR' not in os.environ:
|
||||
print('Variable BUCKET_DIR not set')
|
||||
exit(1)
|
||||
|
||||
if 'REGION' not in os.environ:
|
||||
print('Variable REGION not set')
|
||||
exit(1)
|
||||
12
build/frappe-worker-arm/common/commands/worker.py
Normal file
12
build/frappe-worker-arm/common/commands/worker.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import os
|
||||
from frappe.utils.background_jobs import start_worker
|
||||
|
||||
|
||||
def main():
|
||||
queue = os.environ.get("WORKER_TYPE", "default")
|
||||
start_worker(queue, False)
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"db_host": "${DB_HOST}",
|
||||
"db_port": ${DB_PORT},
|
||||
"redis_cache": "redis://${REDIS_CACHE}",
|
||||
"redis_queue": "redis://${REDIS_QUEUE}",
|
||||
"redis_socketio": "redis://${REDIS_SOCKETIO}",
|
||||
"socketio_port": ${SOCKETIO_PORT}
|
||||
}
|
||||
104
build/frappe-worker-arm/common/nginx-default.conf.template
Normal file
104
build/frappe-worker-arm/common/nginx-default.conf.template
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
upstream frappe-server {
|
||||
server ${FRAPPE_PY}:${FRAPPE_PY_PORT} fail_timeout=0;
|
||||
}
|
||||
|
||||
upstream socketio-server {
|
||||
server ${FRAPPE_SOCKETIO}:${SOCKETIO_PORT} fail_timeout=0;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name $http_host;
|
||||
root /var/www/html;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
|
||||
location /assets {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~ ^/protected/(.*) {
|
||||
internal;
|
||||
try_files /sites/$http_host/$1 =404;
|
||||
}
|
||||
|
||||
location /socket.io {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header X-Frappe-Site-Name $host;
|
||||
proxy_set_header Origin $scheme://$http_host;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass http://socketio-server;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^(.+)/$ $1 permanent;
|
||||
rewrite ^(.+)/index\.html$ $1 permanent;
|
||||
rewrite ^(.+)\.html$ $1 permanent;
|
||||
|
||||
location ~ ^/files/.*.(htm|html|svg|xml) {
|
||||
add_header Content-disposition "attachment";
|
||||
try_files /sites/$http_host/public/$uri @webserver;
|
||||
}
|
||||
|
||||
try_files /sites/$http_host/public/$uri @webserver;
|
||||
}
|
||||
|
||||
location @webserver {
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Frappe-Site-Name $host;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Use-X-Accel-Redirect True;
|
||||
proxy_read_timeout 120;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_pass http://frappe-server;
|
||||
}
|
||||
|
||||
# error pages
|
||||
error_page 502 /502.html;
|
||||
location /502.html {
|
||||
root /var/www/templates;
|
||||
internal;
|
||||
}
|
||||
|
||||
# optimizations
|
||||
sendfile on;
|
||||
keepalive_timeout 15;
|
||||
client_max_body_size 50m;
|
||||
client_body_buffer_size 16K;
|
||||
client_header_buffer_size 1k;
|
||||
|
||||
# enable gzip compresion
|
||||
# based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge
|
||||
gzip on;
|
||||
gzip_http_version 1.1;
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 256;
|
||||
gzip_proxied any;
|
||||
gzip_vary on;
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/javascript
|
||||
application/json
|
||||
application/rss+xml
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
application/font-woff
|
||||
application/x-web-app-manifest+json
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/opentype
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
text/css
|
||||
text/plain
|
||||
text/x-component;
|
||||
# text/html is always compressed by HttpGzipModule
|
||||
}
|
||||
19
build/frappe-worker-arm/common/worker/bench
Normal file
19
build/frappe-worker-arm/common/worker/bench
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#!/home/frappe/frappe-bench/env/bin/python
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bench_dir = os.path.join(os.sep, 'home', 'frappe', 'frappe-bench')
|
||||
sites_dir = os.path.join(bench_dir, 'sites')
|
||||
bench_helper = os.path.join(
|
||||
bench_dir, 'apps', 'frappe',
|
||||
'frappe', 'utils', 'bench_helper.py',
|
||||
)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(sites_dir)
|
||||
subprocess.check_call(
|
||||
[sys.executable, bench_helper, 'frappe'] + sys.argv[1:],
|
||||
)
|
||||
220
build/frappe-worker-arm/common/worker/docker-entrypoint.sh
Normal file
220
build/frappe-worker-arm/common/worker/docker-entrypoint.sh
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
#!/bin/bash
|
||||
|
||||
function configureEnv() {
|
||||
if [ ! -f /home/frappe/frappe-bench/sites/common_site_config.json ]; then
|
||||
|
||||
if [[ -z "$MARIADB_HOST" ]]; then
|
||||
if [[ -z "$POSTGRES_HOST" ]]; then
|
||||
echo "MARIADB_HOST or POSTGRES_HOST is not set"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$REDIS_CACHE" ]]; then
|
||||
echo "REDIS_CACHE is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$REDIS_QUEUE" ]]; then
|
||||
echo "REDIS_QUEUE is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$REDIS_SOCKETIO" ]]; then
|
||||
echo "REDIS_SOCKETIO is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$SOCKETIO_PORT" ]]; then
|
||||
echo "SOCKETIO_PORT is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$DB_PORT" ]]; then
|
||||
export DB_PORT=3306
|
||||
fi
|
||||
|
||||
export DB_HOST="${MARIADB_HOST:-$POSTGRES_HOST}"
|
||||
|
||||
envsubst '${DB_HOST}
|
||||
${DB_PORT}
|
||||
${REDIS_CACHE}
|
||||
${REDIS_QUEUE}
|
||||
${REDIS_SOCKETIO}
|
||||
${SOCKETIO_PORT}' < /opt/frappe/common_site_config.json.template > /home/frappe/frappe-bench/sites/common_site_config.json
|
||||
fi
|
||||
}
|
||||
|
||||
function checkConnection() {
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/check_connection.py"
|
||||
}
|
||||
|
||||
function checkConfigExists() {
|
||||
COUNTER=0
|
||||
while [[ ! -e /home/frappe/frappe-bench/sites/common_site_config.json ]] && [[ $COUNTER -le 30 ]] ; do
|
||||
sleep 1
|
||||
(( COUNTER=COUNTER+1 ))
|
||||
echo "config file not created, retry $COUNTER"
|
||||
done
|
||||
|
||||
if [[ ! -e /home/frappe/frappe-bench/sites/common_site_config.json ]]; then
|
||||
echo "timeout: config file not created"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ ! -e /home/frappe/frappe-bench/sites/apps.txt ]]; then
|
||||
find /home/frappe/frappe-bench/apps -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort -r > /home/frappe/frappe-bench/sites/apps.txt
|
||||
fi
|
||||
|
||||
# Allow user process to create files in logs directory
|
||||
chown -R frappe:frappe /home/frappe/frappe-bench/logs
|
||||
|
||||
# symlink node_modules
|
||||
ln -sfn /home/frappe/frappe-bench/sites/assets/frappe/node_modules \
|
||||
/home/frappe/frappe-bench/apps/frappe/node_modules
|
||||
|
||||
if [ "$1" = 'start' ]; then
|
||||
configureEnv
|
||||
checkConnection
|
||||
|
||||
chown frappe:frappe /home/frappe/frappe-bench/sites/common_site_config.json
|
||||
|
||||
if [[ -z "$WORKERS" ]]; then
|
||||
export WORKERS=2
|
||||
fi
|
||||
|
||||
if [[ -z "$FRAPPE_PORT" ]]; then
|
||||
export FRAPPE_PORT=8000
|
||||
fi
|
||||
|
||||
if [[ ! -z "$AUTO_MIGRATE" ]]; then
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/auto_migrate.py"
|
||||
fi
|
||||
|
||||
if [[ -z "$RUN_AS_ROOT" ]]; then
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& gunicorn -b 0.0.0.0:$FRAPPE_PORT \
|
||||
--worker-tmp-dir /dev/shm \
|
||||
--threads=4 \
|
||||
--workers $WORKERS \
|
||||
--worker-class=gthread \
|
||||
--log-file=- \
|
||||
-t 120 frappe.app:application --preload"
|
||||
else
|
||||
. /home/frappe/frappe-bench/env/bin/activate
|
||||
gunicorn -b 0.0.0.0:$FRAPPE_PORT \
|
||||
--worker-tmp-dir /dev/shm \
|
||||
--threads=4 \
|
||||
--workers $WORKERS \
|
||||
--worker-class=gthread \
|
||||
--log-file=- \
|
||||
-t 120 frappe.app:application --preload
|
||||
fi
|
||||
|
||||
elif [ "$1" = 'worker' ]; then
|
||||
checkConfigExists
|
||||
checkConnection
|
||||
# default WORKER_TYPE=default
|
||||
if [[ -z "$RUN_AS_ROOT" ]]; then
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/worker.py"
|
||||
else
|
||||
. /home/frappe/frappe-bench/env/bin/activate
|
||||
python /home/frappe/frappe-bench/commands/worker.py
|
||||
fi
|
||||
|
||||
elif [ "$1" = 'schedule' ]; then
|
||||
checkConfigExists
|
||||
checkConnection
|
||||
if [[ -z "$RUN_AS_ROOT" ]]; then
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/background.py"
|
||||
else
|
||||
. /home/frappe/frappe-bench/env/bin/activate
|
||||
python /home/frappe/frappe-bench/commands/background.py
|
||||
fi
|
||||
|
||||
elif [ "$1" = 'new' ]; then
|
||||
checkConfigExists
|
||||
checkConnection
|
||||
if [[ -z "$RUN_AS_ROOT" ]]; then
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/new.py"
|
||||
exit
|
||||
else
|
||||
. /home/frappe/frappe-bench/env/bin/activate
|
||||
python /home/frappe/frappe-bench/commands/new.py
|
||||
fi
|
||||
|
||||
elif [ "$1" = 'drop' ]; then
|
||||
checkConfigExists
|
||||
checkConnection
|
||||
if [[ -z "$RUN_AS_ROOT" ]]; then
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/drop.py"
|
||||
exit
|
||||
else
|
||||
. /home/frappe/frappe-bench/env/bin/activate
|
||||
python /home/frappe/frappe-bench/commands/drop.py
|
||||
fi
|
||||
|
||||
elif [ "$1" = 'migrate' ]; then
|
||||
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/migrate.py"
|
||||
exit
|
||||
|
||||
elif [ "$1" = 'doctor' ]; then
|
||||
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/doctor.py ${@:2}"
|
||||
exit
|
||||
|
||||
elif [ "$1" = 'backup' ]; then
|
||||
|
||||
if [[ -z "$RUN_AS_ROOT" ]]; then
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/backup.py"
|
||||
exit
|
||||
else
|
||||
. /home/frappe/frappe-bench/env/bin/activate
|
||||
python /home/frappe/frappe-bench/commands/backup.py
|
||||
fi
|
||||
|
||||
elif [ "$1" = 'console' ]; then
|
||||
|
||||
if [[ -z "$2" ]]; then
|
||||
echo "Need to specify a sitename with the command:"
|
||||
echo "console <sitename>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$RUN_AS_ROOT" ]]; then
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/console.py $2"
|
||||
exit
|
||||
else
|
||||
. /home/frappe/frappe-bench/env/bin/activate
|
||||
python /home/frappe/frappe-bench/commands/console.py "$2"
|
||||
fi
|
||||
|
||||
elif [ "$1" = 'push-backup' ]; then
|
||||
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/push_backup.py"
|
||||
exit
|
||||
|
||||
elif [ "$1" = 'restore-backup' ]; then
|
||||
|
||||
su frappe -c ". /home/frappe/frappe-bench/env/bin/activate \
|
||||
&& python /home/frappe/frappe-bench/commands/restore_backup.py"
|
||||
exit
|
||||
|
||||
else
|
||||
|
||||
exec $@
|
||||
|
||||
fi
|
||||
40
build/frappe-worker-arm/common/worker/healthcheck.sh
Normal file
40
build/frappe-worker-arm/common/worker/healthcheck.sh
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
|
||||
export COMMON_SITE_CONFIG_JSON='/home/frappe/frappe-bench/sites/common_site_config.json'
|
||||
|
||||
# Set DB Host and port
|
||||
export DB_HOST=`cat $COMMON_SITE_CONFIG_JSON | awk '/db_host/ { gsub(/[",]/,"",$2); print $2}' | tr -d '\n'`
|
||||
export DB_PORT=`cat $COMMON_SITE_CONFIG_JSON | awk '/db_port/ { gsub(/[",]/,"",$2); print $2}' | tr -d '\n'`
|
||||
if [[ -z "$DB_PORT" ]]; then
|
||||
export DB_PORT=3306
|
||||
fi
|
||||
|
||||
# Set REDIS host:port
|
||||
export REDIS_CACHE=`cat $COMMON_SITE_CONFIG_JSON | awk '/redis_cache/ { gsub(/[",]/,"",$2); print $2}' | tr -d '\n' | sed 's|redis://||g'`
|
||||
if [[ "$REDIS_CACHE" == *"/"* ]]; then
|
||||
export REDIS_CACHE=`echo $REDIS_CACHE | cut -f1 -d"/"`
|
||||
fi
|
||||
|
||||
export REDIS_QUEUE=`cat $COMMON_SITE_CONFIG_JSON | awk '/redis_queue/ { gsub(/[",]/,"",$2); print $2}' | tr -d '\n' | sed 's|redis://||g'`
|
||||
if [[ "$REDIS_QUEUE" == *"/"* ]]; then
|
||||
export REDIS_QUEUE=`echo $REDIS_QUEUE | cut -f1 -d"/"`
|
||||
fi
|
||||
|
||||
export REDIS_SOCKETIO=`cat $COMMON_SITE_CONFIG_JSON | awk '/redis_socketio/ { gsub(/[",]/,"",$2); print $2}' | tr -d '\n' | sed 's|redis://||g'`
|
||||
if [[ "$REDIS_SOCKETIO" == *"/"* ]]; then
|
||||
export REDIS_SOCKETIO=`echo $REDIS_SOCKETIO | cut -f1 -d"/"`
|
||||
fi
|
||||
|
||||
echo "Check $DB_HOST:$DB_PORT"
|
||||
wait-for-it $DB_HOST:$DB_PORT -t 1
|
||||
echo "Check $REDIS_CACHE"
|
||||
wait-for-it $REDIS_CACHE -t 1
|
||||
echo "Check $REDIS_QUEUE"
|
||||
wait-for-it $REDIS_QUEUE -t 1
|
||||
echo "Check $REDIS_SOCKETIO"
|
||||
wait-for-it $REDIS_SOCKETIO -t 1
|
||||
|
||||
if [[ "$1" = "-p" ]] || [[ "$1" = "--ping-service" ]]; then
|
||||
echo "Check $2"
|
||||
wait-for-it $2 -t 1
|
||||
fi
|
||||
16
build/frappe-worker-arm/common/worker/install_app.sh
Normal file
16
build/frappe-worker-arm/common/worker/install_app.sh
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
APP_NAME=${1}
|
||||
APP_REPO=${2}
|
||||
APP_BRANCH=${3}
|
||||
|
||||
cd /home/frappe/frappe-bench/
|
||||
|
||||
. env/bin/activate
|
||||
|
||||
cd ./apps
|
||||
|
||||
[ "${APP_BRANCH}" ] && BRANCH="-b ${APP_BRANCH}"
|
||||
|
||||
git clone --depth 1 -o upstream ${APP_REPO} ${BRANCH} ${APP_NAME}
|
||||
pip3 install --no-cache-dir -e /home/frappe/frappe-bench/apps/${APP_NAME}
|
||||
0
frappe-installer
Executable file → Normal file
0
frappe-installer
Executable file → Normal file
0
tests/docker-test.sh
Executable file → Normal file
0
tests/docker-test.sh
Executable file → Normal file
0
travis.py
Executable file → Normal file
0
travis.py
Executable file → Normal file
Loading…
Reference in a new issue