mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
Auto-commit on 2024-10-15 10:31:15 by pcx3
This commit is contained in:
22
configuration/.github/scripts/get_cf_ips.sh
vendored
22
configuration/.github/scripts/get_cf_ips.sh
vendored
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
cf_ips="$(curl -fsLm5 --retry 3 https://api.cloudflare.com/client/v4/ips)"
|
||||
|
||||
if [ -n "$cf_ips" ] && [ "$(echo "$cf_ips" | jq -r '.success')" = "true" ]; then
|
||||
cf_inc="nginx/cloudflare.inc"
|
||||
|
||||
echo "[ * ] Updating Cloudflare IP Ranges for Nginx..."
|
||||
echo "# Cloudflare IP Ranges" > $cf_inc
|
||||
echo "" >> $cf_inc
|
||||
echo "# IPv4" >> $cf_inc
|
||||
for ipv4 in $(echo "$cf_ips" | jq -r '.result.ipv4_cidrs[]' | sort); do
|
||||
echo "set_real_ip_from $ipv4;" >> $cf_inc
|
||||
done
|
||||
echo "" >> $cf_inc
|
||||
echo "# IPv6" >> $cf_inc
|
||||
for ipv6 in $(echo "$cf_ips" | jq -r '.result.ipv6_cidrs[]' | sort); do
|
||||
echo "set_real_ip_from $ipv6;" >> $cf_inc
|
||||
done
|
||||
echo "" >> $cf_inc
|
||||
echo "real_ip_header CF-Connecting-IP;" >> $cf_inc
|
||||
fi
|
||||
21
configuration/.github/workflows/trigger-sync.yml
vendored
21
configuration/.github/workflows/trigger-sync.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Trigger sync of conf
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main #always
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
repository-projects: write
|
||||
steps:
|
||||
- name: Trigger Sync
|
||||
run: |
|
||||
curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/stefanpejcic/OpenPanel/dispatches \
|
||||
-d '{"event_type":"sync-configuration"}'
|
||||
@@ -1,36 +0,0 @@
|
||||
name: Update Cloudflare IPs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
workflow_dispatch: # manual trigger
|
||||
|
||||
permissions:
|
||||
contents: write # to push
|
||||
|
||||
jobs:
|
||||
update_cf_ips:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Ensure get_cf_ips.sh is executable
|
||||
run: chmod +x .github/scripts/get_cf_ips.sh
|
||||
|
||||
- name: Install jq
|
||||
run: sudo apt-get install -y jq
|
||||
|
||||
- name: Run get_cf_ips.sh
|
||||
run: .github/scripts/get_cf_ips.sh
|
||||
|
||||
- name: Commit and push changes
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add nginx/cloudflare.inc
|
||||
git commit -m "Update Cloudflare IPs" || echo "No changes to commit"
|
||||
git push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Update Serial Number Daily
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Runs every day at midnight UTC
|
||||
workflow_dispatch: # Allows manual triggering
|
||||
|
||||
jobs:
|
||||
update-serial:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
- name: Update serial number
|
||||
run: |
|
||||
# Get today's date in YYYYMMDD format
|
||||
DATE=$(date -u +"%Y%m%d")
|
||||
|
||||
FILE="bind9/zone_template.txt"
|
||||
|
||||
sed -i "s/[0-9]\{10\}/$(date +%Y%m%d)01/" $FILE
|
||||
|
||||
- name: Commit changes
|
||||
uses: EndBug/add-and-commit@v9
|
||||
with:
|
||||
add: 'bind9/zone_template.txt'
|
||||
message: 'Update serial number with current date'
|
||||
author_name: 'GitHub Actions'
|
||||
author_email: 'actions@github.com'
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
3
configuration/.gitignore
vendored
3
configuration/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
#.github/
|
||||
#.github/workflows/
|
||||
README.md
|
||||
4
configuration/README.md
Normal file
4
configuration/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# openpanel-configuration
|
||||
Configuration files for `/etc/openpanel/`
|
||||
|
||||
# ✌️
|
||||
@@ -1,6 +1,6 @@
|
||||
$TTL 1h
|
||||
@ IN SOA {ns1}. {ns2}. (
|
||||
2024091801 ; Serial number
|
||||
2024101501 ; Serial number
|
||||
1h ; Refresh interval
|
||||
15m ; Retry interval
|
||||
1w ; Expire interval
|
||||
|
||||
@@ -14,9 +14,9 @@ services:
|
||||
volumes:
|
||||
- openadmin_mysql:/var/lib/mysql
|
||||
- /etc/openpanel/:/etc/openpanel/
|
||||
- /etc/openpanel/docker/compose/initialize.sql:/docker-entrypoint-initdb.d/initialize.sql
|
||||
mem_limit: 1g
|
||||
cpus: 1
|
||||
- /root/initialize.sql:/docker-entrypoint-initdb.d/initialize.sql
|
||||
mem_limit: 0.5g
|
||||
cpus: 1.0
|
||||
oom_kill_disable: true
|
||||
|
||||
# OpenPanel service running on port 2083
|
||||
@@ -37,80 +37,137 @@ services:
|
||||
- /sys:/host/sys:ro
|
||||
- /:/hostfs:ro
|
||||
- /home:/home
|
||||
- /etc/ufw:/etc/ufw
|
||||
- /usr/local/admin:/usr/local/admin
|
||||
- /usr/local/admin/scripts:/usr/local/admin/scripts
|
||||
- /var/log:/var/log
|
||||
- /etc/ufw:/etc/ufw
|
||||
- /etc/letsencrypt:/etc/letsencrypt
|
||||
- /etc/my.cnf:/etc/my.cnf
|
||||
- /etc/openpanel/:/etc/openpanel/
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- openadmin_mysql:/var/lib/mysql
|
||||
- /usr/bin/docker:/usr/bin/docker
|
||||
- /root/:/root/
|
||||
# https://dev.openpanel.com/customize.html#Custom-Code
|
||||
- /etc/openpanel/openpanel/custom_code/:/usr/local/panel/templates/custom_code/
|
||||
- /etc/openpanel/openpanel/custom_code/custom.css:/usr/local/panel/static/css/custom.css
|
||||
- /etc/openpanel/openpanel/custom_code/custom.js:/usr/local/panel/static/js/custom.js
|
||||
- /etc/openpanel/openpanel/conf/knowledge_base_articles.json:/etc/openpanel/openpanel/conf/knowledge_base_articles.json
|
||||
network_mode: host
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1g
|
||||
cpus: '1.0'
|
||||
mem_limit: 1g
|
||||
cpus: 1.0
|
||||
restart: always
|
||||
privileged: true
|
||||
|
||||
#mailserver:
|
||||
# image: ghcr.io/docker-mailserver/docker-mailserver:latest
|
||||
# container_name: openadmin_mailserver
|
||||
# hostname: mail.openpanel.site
|
||||
# env_file: mailserver.env
|
||||
# ports:
|
||||
# - "25:25"
|
||||
# - "143:143"
|
||||
# - "465:465"
|
||||
# - "587:587"
|
||||
# - "993:993"
|
||||
# volumes:
|
||||
# - ./docker-data/dms/mail-data/:/var/mail/
|
||||
# - ./docker-data/dms/mail-state/:/var/mail-state/
|
||||
# - ./docker-data/dms/mail-logs/:/var/log/mail/
|
||||
# - ./docker-data/dms/config/:/tmp/docker-mailserver/
|
||||
# - /etc/localtime:/etc/localtime:ro
|
||||
# restart: always
|
||||
# stop_grace_period: 1m
|
||||
# healthcheck:
|
||||
# test: "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1"
|
||||
# timeout: 3s
|
||||
# retries: 0
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# memory: 1g
|
||||
# cpus: '1.0'
|
||||
# networks:
|
||||
# - openadmin_mail_network
|
||||
# Webserver from 0.2.5+
|
||||
nginx:
|
||||
image: openpanel/waf # openpanel/waf for modsecurity or nginx:alpine
|
||||
container_name: nginx
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- /etc/openpanel/nginx/nginx.conf:/usr/local/nginx/conf/nginx.conf # for modsecurity
|
||||
# - /etc/openpanel/nginx/nginx.conf:/etc/nginx/nginx.conf # for standalone nginx
|
||||
- /etc/openpanel/nginx/vhosts/default.conf:/etc/nginx/conf.d/default.conf
|
||||
- /etc/openpanel/nginx/vhosts/openpanel_proxy.conf:/etc/openpanel/nginx/vhosts/openpanel_proxy.conf
|
||||
- /etc/nginx/sites-available/:/etc/nginx/sites-available/
|
||||
- /etc/nginx/sites-enabled/:/etc/nginx/sites-enabled/
|
||||
- /etc/nginx/ssl/:/etc/nginx/ssl/ # for custom ssl from 0.2.6
|
||||
- /etc/openpanel/nginx/error_pages/snippets/:/usr/local/nginx/conf/snippets/
|
||||
- /etc/openpanel/nginx/error_pages/:/srv/http/default/
|
||||
- /var/log/nginx/:/var/log/nginx/
|
||||
- /etc/letsencrypt/options-ssl-nginx.conf:/etc/letsencrypt/options-ssl-nginx.conf
|
||||
- /etc/letsencrypt/ssl-dhparams.pem:/etc/letsencrypt/ssl-dhparams.pem
|
||||
- /etc/letsencrypt/:/etc/letsencrypt/
|
||||
- /etc/openpanel/openpanel/core/users/:/etc/openpanel/openpanel/core/users/
|
||||
- /etc/hosts:/etc/hosts
|
||||
- /usr/share/nginx/html/:/usr/share/nginx/html/
|
||||
- /home/:/home/:ro
|
||||
# start modsecurity #
|
||||
- /etc/openpanel/nginx/modsecurity/modsec_includes.conf:/usr/local/nginx/conf/modsec_includes.conf
|
||||
- /etc/openpanel/nginx/modsecurity/modsecurity.conf:/usr/local/nginx/conf/modsecurity.conf
|
||||
- /etc/openpanel/nginx/modsecurity/crs-setup.conf:/usr/local/nginx/conf/rules/crs-setup.conf
|
||||
- /etc/openpanel/nginx/modsecurity/rules/:/usr/local/nginx/conf/rules/
|
||||
- /etc/openpanel/nginx/modsecurity/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf:/usr/local/nginx/conf/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
|
||||
- /etc/openpanel/nginx/modsecurity/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:/usr/local/nginx/conf/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
|
||||
# end modsecurity #
|
||||
restart: unless-stopped
|
||||
mem_limit: 1g
|
||||
cpus: 1.0
|
||||
oom_kill_disable: true
|
||||
|
||||
# SSL status and renewals
|
||||
certbot:
|
||||
image: certbot/certbot:latest
|
||||
container_name: certbot
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- /etc/letsencrypt:/etc/letsencrypt # Let's Encrypt certificates
|
||||
- /var/lib/letsencrypt:/var/lib/letsencrypt # Working directory for Certbot
|
||||
- /etc/nginx/sites-available:/etc/nginx/sites-available # Access to Nginx config for authentication challenges
|
||||
- /etc/nginx/sites-enabled:/etc/nginx/sites-enabled # Enabled sites for reloading after cert issuance
|
||||
entrypoint: /bin/sh -c 'trap exit TERM; while :; do sleep 6h & wait $${!}; certbot renew; nginx -s reload; done'
|
||||
restart: unless-stopped
|
||||
mem_limit: 0.1g
|
||||
cpus: 0.1
|
||||
oom_kill_disable: true
|
||||
|
||||
#roundcube:
|
||||
# image: roundcube/roundcubemail
|
||||
# container_name: openadmin_roundcube
|
||||
# restart: always
|
||||
# environment:
|
||||
# - ROUNDCUBEMAIL_DEFAULT_HOST=openadmin_mailserver
|
||||
# - ROUNDCUBEMAIL_SMTP_SERVER=openadmin_mailserver
|
||||
# ports:
|
||||
# - "8080:80"
|
||||
# networks:
|
||||
# - openadmin_mail_network
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# memory: 1g
|
||||
# cpus: '1.0'
|
||||
# DNS
|
||||
bind9:
|
||||
container_name: openpanel_dns
|
||||
image: ubuntu/bind9:latest
|
||||
environment:
|
||||
- BIND9_USER=root
|
||||
- TZ=America/New_York
|
||||
ports:
|
||||
- "53:53/tcp"
|
||||
- "53:53/udp"
|
||||
volumes:
|
||||
- /etc/bind/:/etc/bind/
|
||||
restart: unless-stopped
|
||||
mem_limit: 0.1g
|
||||
cpus: 0.1
|
||||
oom_kill_disable: true
|
||||
|
||||
# FTP
|
||||
ftp_env_generator:
|
||||
image: alpine:latest
|
||||
container_name: ftp_env_generator
|
||||
volumes:
|
||||
- /etc/openpanel/ftp/:/etc/openpanel/ftp/
|
||||
- /usr/local/admin/scripts/ftp/users:/usr/local/admin/scripts/ftp/users
|
||||
entrypoint: /bin/sh -c "/usr/local/admin/scripts/ftp/users"
|
||||
restart: "no" # Do not restart, we just want it to run once
|
||||
|
||||
#networks:
|
||||
# openadmin_mail_network:
|
||||
# driver: bridge
|
||||
openadmin_ftp:
|
||||
#OLD# image: delfer/alpine-ftp-server
|
||||
build:
|
||||
context: /etc/openpanel/ftp/
|
||||
container_name: openadmin_ftp
|
||||
restart: always
|
||||
ports:
|
||||
- "21:21"
|
||||
- "21000-21010:21000-21010"
|
||||
volumes:
|
||||
- /home/:/home/
|
||||
- /etc/openpanel/ftp/vsftpd.conf:/etc/vsftpd/vsftpd.conf
|
||||
- /etc/openpanel/ftp/start_vsftpd.sh:/bin/start_vsftpd.sh
|
||||
- /etc/openpanel/ftp/vsftpd.chroot_list:/etc/vsftpd.chroot_list
|
||||
- /etc/openpanel/users/:/etc/openpanel/ftp/users/
|
||||
# uncomment for ssl # - /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
depends_on:
|
||||
- ftp_env_generator
|
||||
env_file:
|
||||
- /etc/openpanel/ftp/all.users
|
||||
# uncomment the following lines for SSL and replace ftp.YOUR_DOMAIN_HERE.com with your domain
|
||||
# environment:
|
||||
# - ADDRESS=ftp.YOUR_DOMAIN_HERE.com
|
||||
# - TLS_CERT="/etc/letsencrypt/live/ftp.YOUR_DOMAIN_HERE.com/fullchain.pem"
|
||||
# - TLS_KEY="/etc/letsencrypt/live/ftp.YOUR_DOMAIN_HERE.com/privkey.pem"
|
||||
mem_limit: 0.5g
|
||||
cpus: 0.5
|
||||
|
||||
|
||||
|
||||
# make the data persistent
|
||||
# make the mysql data persistent
|
||||
volumes:
|
||||
openadmin_mysql:
|
||||
|
||||
@@ -127,10 +127,9 @@ services:
|
||||
- /etc/openpanel/ftp/:/etc/openpanel/ftp/
|
||||
- /usr/local/admin/scripts/ftp/users:/usr/local/admin/scripts/ftp/users
|
||||
entrypoint: /bin/sh -c "/usr/local/admin/scripts/ftp/users"
|
||||
restart: "no" # Do not restart, we just want it to run once
|
||||
restart: "no" # only run once
|
||||
|
||||
openadmin_ftp:
|
||||
#OLD# image: delfer/alpine-ftp-server
|
||||
build:
|
||||
context: /etc/openpanel/ftp/
|
||||
container_name: openadmin_ftp
|
||||
@@ -144,16 +143,10 @@ services:
|
||||
- /etc/openpanel/ftp/start_vsftpd.sh:/bin/start_vsftpd.sh
|
||||
- /etc/openpanel/ftp/vsftpd.chroot_list:/etc/vsftpd.chroot_list
|
||||
- /etc/openpanel/users/:/etc/openpanel/ftp/users/
|
||||
# uncomment for ssl # - /etc/letsencrypt:/etc/letsencrypt:ro
|
||||
depends_on:
|
||||
- ftp_env_generator
|
||||
env_file:
|
||||
- /etc/openpanel/ftp/all.users
|
||||
# uncomment the following lines for SSL and replace ftp.YOUR_DOMAIN_HERE.com with your domain
|
||||
# environment:
|
||||
# - ADDRESS=ftp.YOUR_DOMAIN_HERE.com
|
||||
# - TLS_CERT="/etc/letsencrypt/live/ftp.YOUR_DOMAIN_HERE.com/fullchain.pem"
|
||||
# - TLS_KEY="/etc/letsencrypt/live/ftp.YOUR_DOMAIN_HERE.com/privkey.pem"
|
||||
mem_limit: 0.5g
|
||||
cpus: 0.5
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ CREATE TABLE `users` (
|
||||
`otp_secret` varchar(255) DEFAULT NULL,
|
||||
`plan` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
|
||||
`registered_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`server` varchar(255) DEFAULT 'default',
|
||||
`plan_id` int DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ CREATE TABLE `users` (
|
||||
`otp_secret` varchar(255) DEFAULT NULL,
|
||||
`plan` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
|
||||
`registered_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`server` varchar(255) DEFAULT 'default',
|
||||
`plan_id` int DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
|
||||
24
configuration/nginx/modsecurity/modsec_includes.conf
Normal file
24
configuration/nginx/modsecurity/modsec_includes.conf
Normal file
@@ -0,0 +1,24 @@
|
||||
include rules/crs-setup.conf
|
||||
include rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
|
||||
include rules/REQUEST-901-INITIALIZATION.conf
|
||||
include rules/REQUEST-905-COMMON-EXCEPTIONS.conf
|
||||
include rules/REQUEST-911-METHOD-ENFORCEMENT.conf
|
||||
include rules/REQUEST-913-SCANNER-DETECTION.conf
|
||||
include rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf
|
||||
include rules/REQUEST-921-PROTOCOL-ATTACK.conf
|
||||
include rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf
|
||||
include rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf
|
||||
include rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf
|
||||
include rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf
|
||||
include rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
|
||||
include rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
|
||||
include rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf
|
||||
include rules/REQUEST-949-BLOCKING-EVALUATION.conf
|
||||
include rules/RESPONSE-950-DATA-LEAKAGES.conf
|
||||
include rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf
|
||||
include rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
|
||||
include rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
|
||||
include rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
|
||||
include rules/RESPONSE-959-BLOCKING-EVALUATION.conf
|
||||
include rules/RESPONSE-980-CORRELATION.conf
|
||||
include rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
|
||||
228
configuration/nginx/modsecurity/modsecurity.conf
Normal file
228
configuration/nginx/modsecurity/modsecurity.conf
Normal file
@@ -0,0 +1,228 @@
|
||||
# -- Rule engine initialization ----------------------------------------------
|
||||
|
||||
# Enable ModSecurity, attaching it to every transaction. Use detection
|
||||
# only to start with, because that minimises the chances of post-installation
|
||||
# disruption.
|
||||
#
|
||||
SecRuleEngine DetectionOnly
|
||||
|
||||
|
||||
# -- Request body handling ---------------------------------------------------
|
||||
|
||||
# Allow ModSecurity to access request bodies. If you don't, ModSecurity
|
||||
# won't be able to see any POST parameters, which opens a large security
|
||||
# hole for attackers to exploit.
|
||||
#
|
||||
SecRequestBodyAccess On
|
||||
|
||||
|
||||
# Enable XML request body parser.
|
||||
# Initiate XML Processor in case of xml content-type
|
||||
#
|
||||
SecRule REQUEST_HEADERS:Content-Type "(?:text|application)/xml" \
|
||||
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
|
||||
|
||||
# Enable JSON request body parser.
|
||||
# Initiate JSON Processor in case of JSON content-type; change accordingly
|
||||
# if your application does not use 'application/json'
|
||||
#
|
||||
SecRule REQUEST_HEADERS:Content-Type "application/json" \
|
||||
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
|
||||
|
||||
# Maximum request body size we will accept for buffering. If you support
|
||||
# file uploads then the value given on the first line has to be as large
|
||||
# as the largest file you are willing to accept. The second value refers
|
||||
# to the size of data, with files excluded. You want to keep that value as
|
||||
# low as practical.
|
||||
#
|
||||
SecRequestBodyLimit 13107200
|
||||
SecRequestBodyNoFilesLimit 131072
|
||||
|
||||
# Store up to 128 KB of request body data in memory. When the multipart
|
||||
# parser reachers this limit, it will start using your hard disk for
|
||||
# storage. That is slow, but unavoidable.
|
||||
# Obsolete
|
||||
#SecRequestBodyInMemoryLimit 131072
|
||||
|
||||
# What do do if the request body size is above our configured limit.
|
||||
# Keep in mind that this setting will automatically be set to ProcessPartial
|
||||
# when SecRuleEngine is set to DetectionOnly mode in order to minimize
|
||||
# disruptions when initially deploying ModSecurity.
|
||||
#
|
||||
SecRequestBodyLimitAction Reject
|
||||
|
||||
# Verify that we've correctly processed the request body.
|
||||
# As a rule of thumb, when failing to process a request body
|
||||
# you should reject the request (when deployed in blocking mode)
|
||||
# or log a high-severity alert (when deployed in detection-only mode).
|
||||
#
|
||||
SecRule REQBODY_ERROR "!@eq 0" \
|
||||
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
|
||||
|
||||
# By default be strict with what we accept in the multipart/form-data
|
||||
# request body. If the rule below proves to be too strict for your
|
||||
# environment consider changing it to detection-only. You are encouraged
|
||||
# _not_ to remove it altogether.
|
||||
#
|
||||
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
|
||||
"id:'200003',phase:2,t:none,log,deny,status:400, \
|
||||
msg:'Multipart request body failed strict validation: \
|
||||
PE %{REQBODY_PROCESSOR_ERROR}, \
|
||||
BQ %{MULTIPART_BOUNDARY_QUOTED}, \
|
||||
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
|
||||
DB %{MULTIPART_DATA_BEFORE}, \
|
||||
DA %{MULTIPART_DATA_AFTER}, \
|
||||
HF %{MULTIPART_HEADER_FOLDING}, \
|
||||
LF %{MULTIPART_LF_LINE}, \
|
||||
SM %{MULTIPART_MISSING_SEMICOLON}, \
|
||||
IQ %{MULTIPART_INVALID_QUOTING}, \
|
||||
IP %{MULTIPART_INVALID_PART}, \
|
||||
IH %{MULTIPART_INVALID_HEADER_FOLDING}, \
|
||||
FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
|
||||
|
||||
# Did we see anything that might be a boundary?
|
||||
#
|
||||
SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \
|
||||
"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'"
|
||||
|
||||
# PCRE Tuning
|
||||
# We want to avoid a potential RegEx DoS condition
|
||||
#
|
||||
SecPcreMatchLimit 1000
|
||||
SecPcreMatchLimitRecursion 1000
|
||||
|
||||
# Some internal errors will set flags in TX and we will need to look for these.
|
||||
# All of these are prefixed with "MSC_". The following flags currently exist:
|
||||
#
|
||||
# MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded.
|
||||
#
|
||||
SecRule TX:/^MSC_/ "!@streq 0" \
|
||||
"id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'"
|
||||
|
||||
|
||||
# -- Response body handling --------------------------------------------------
|
||||
|
||||
# Allow ModSecurity to access response bodies.
|
||||
# You should have this directive enabled in order to identify errors
|
||||
# and data leakage issues.
|
||||
#
|
||||
# Do keep in mind that enabling this directive does increases both
|
||||
# memory consumption and response latency.
|
||||
#
|
||||
SecResponseBodyAccess On
|
||||
|
||||
# Which response MIME types do you want to inspect? You should adjust the
|
||||
# configuration below to catch documents but avoid static files
|
||||
# (e.g., images and archives).
|
||||
#
|
||||
SecResponseBodyMimeType text/plain text/html text/xml
|
||||
|
||||
# Buffer response bodies of up to 512 KB in length.
|
||||
SecResponseBodyLimit 524288
|
||||
|
||||
# What happens when we encounter a response body larger than the configured
|
||||
# limit? By default, we process what we have and let the rest through.
|
||||
# That's somewhat less secure, but does not break any legitimate pages.
|
||||
#
|
||||
SecResponseBodyLimitAction ProcessPartial
|
||||
|
||||
|
||||
# -- Filesystem configuration ------------------------------------------------
|
||||
|
||||
# The location where ModSecurity stores temporary files (for example, when
|
||||
# it needs to handle a file upload that is larger than the configured limit).
|
||||
#
|
||||
# This default setting is chosen due to all systems have /tmp available however,
|
||||
# this is less than ideal. It is recommended that you specify a location that's private.
|
||||
#
|
||||
SecTmpDir /tmp/
|
||||
|
||||
# The location where ModSecurity will keep its persistent data. This default setting
|
||||
# is chosen due to all systems have /tmp available however, it
|
||||
# too should be updated to a place that other users can't access.
|
||||
#
|
||||
SecDataDir /tmp/
|
||||
|
||||
|
||||
# -- File uploads handling configuration -------------------------------------
|
||||
|
||||
# The location where ModSecurity stores intercepted uploaded files. This
|
||||
# location must be private to ModSecurity. You don't want other users on
|
||||
# the server to access the files, do you?
|
||||
#
|
||||
#SecUploadDir /opt/modsecurity/var/upload/
|
||||
|
||||
# By default, only keep the files that were determined to be unusual
|
||||
# in some way (by an external inspection script). For this to work you
|
||||
# will also need at least one file inspection rule.
|
||||
#
|
||||
#SecUploadKeepFiles RelevantOnly
|
||||
|
||||
# Uploaded files are by default created with permissions that do not allow
|
||||
# any other user to access them. You may need to relax that if you want to
|
||||
# interface ModSecurity to an external program (e.g., an anti-virus).
|
||||
#
|
||||
#SecUploadFileMode 0600
|
||||
|
||||
|
||||
# -- Debug log configuration -------------------------------------------------
|
||||
|
||||
# The default debug log configuration is to duplicate the error, warning
|
||||
# and notice messages from the error log.
|
||||
#
|
||||
#SecDebugLog /opt/modsecurity/var/log/debug.log
|
||||
#SecDebugLogLevel 3
|
||||
|
||||
|
||||
# -- Audit log configuration -------------------------------------------------
|
||||
|
||||
# Log the transactions that are marked by a rule, as well as those that
|
||||
# trigger a server error (determined by a 5xx or 4xx, excluding 404,
|
||||
# level response status codes).
|
||||
#
|
||||
SecAuditEngine RelevantOnly
|
||||
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
|
||||
|
||||
# Log everything we know about a transaction.
|
||||
SecAuditLogParts ABIJDEFHZ
|
||||
|
||||
# Use a single file for logging. This is much easier to look at, but
|
||||
# assumes that you will use the audit log only ocassionally.
|
||||
#
|
||||
SecAuditLogType Serial
|
||||
SecAuditLog /var/log/modsec_audit.log
|
||||
|
||||
# Specify the path for concurrent audit logging.
|
||||
SecAuditLogStorageDir /opt/modsecurity/var/audit/
|
||||
|
||||
|
||||
# -- Miscellaneous -----------------------------------------------------------
|
||||
|
||||
# Use the most commonly used application/x-www-form-urlencoded parameter
|
||||
# separator. There's probably only one application somewhere that uses
|
||||
# something else so don't expect to change this value.
|
||||
#
|
||||
SecArgumentSeparator &
|
||||
|
||||
# Settle on version 0 (zero) cookies, as that is what most applications
|
||||
# use. Using an incorrect cookie version may open your installation to
|
||||
# evasion attacks (against the rules that examine named cookies).
|
||||
#
|
||||
SecCookieFormat 0
|
||||
|
||||
# Specify your Unicode Code Point.
|
||||
# This mapping is used by the t:urlDecodeUni transformation function
|
||||
# to properly map encoded data to your language. Properly setting
|
||||
# these directives helps to reduce false positives and negatives.
|
||||
#
|
||||
SecUnicodeMapFile /ModSecurity/unicode.mapping 20127
|
||||
|
||||
# Improve the quality of ModSecurity by sharing information about your
|
||||
# current ModSecurity version and dependencies versions.
|
||||
# The following information will be shared: ModSecurity version,
|
||||
# Web Server version, APR version, PCRE version, Lua version, Libxml2
|
||||
# version, Anonymous unique id for host.
|
||||
SecStatusEngine On
|
||||
|
||||
#Load all Rule
|
||||
Include modsec_includes.conf
|
||||
@@ -12,21 +12,6 @@
|
||||
</Directory>
|
||||
# <!-- END EXPOSED RESOURCES PROTECTION -->
|
||||
|
||||
##### /phmyadmin on #####
|
||||
ProxyRequests Off
|
||||
ProxyPreserveHost On
|
||||
|
||||
<Proxy>
|
||||
Require all granted
|
||||
</Proxy>
|
||||
|
||||
ProxyPass /phpmyadmin http://localhost:8080/
|
||||
ProxyPassReverse /phpmyadmin http://localhost:8080/
|
||||
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
RequestHeader set X-Forwarded-Ssl on
|
||||
##### /phpmyadmin end #####
|
||||
|
||||
<Directory /home/<USER>/<DOMAIN_NAME>>
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
@@ -61,21 +46,6 @@
|
||||
SSLCertificateFile /etc/apache2/ssl/cert.crt
|
||||
SSLCertificateKeyFile /etc/apache2/ssl/cert.key
|
||||
|
||||
##### /phmyadmin on #####
|
||||
ProxyRequests Off
|
||||
ProxyPreserveHost On
|
||||
|
||||
<Proxy>
|
||||
Require all granted
|
||||
</Proxy>
|
||||
|
||||
ProxyPass /phpmyadmin http://localhost:8080/
|
||||
ProxyPassReverse /phpmyadmin http://localhost:8080/
|
||||
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
RequestHeader set X-Forwarded-Ssl on
|
||||
##### /phpmyadmin end #####
|
||||
|
||||
<Directory /home/<USER>/<DOMAIN_NAME>>
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
|
||||
@@ -10,20 +10,7 @@ server {
|
||||
return 403;
|
||||
}
|
||||
# <!-- END EXPOSED RESOURCES PROTECTION -->
|
||||
|
||||
##### /phmyadmin on #####
|
||||
location /phpmyadmin {
|
||||
proxy_pass http://localhost:8080/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Ssl on;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
}
|
||||
##### /phmyadmin off #####
|
||||
|
||||
|
||||
root /home/<USER>/<DOMAIN_NAME>;
|
||||
|
||||
location / {
|
||||
@@ -62,19 +49,6 @@ server {
|
||||
listen 443 ssl http2;
|
||||
server_name <DOMAIN_NAME> www.<DOMAIN_NAME>;
|
||||
access_log /var/log/nginx/domlogs/<DOMAIN_NAME>.log;
|
||||
|
||||
##### /phmyadmin on #####
|
||||
location /phpmyadmin {
|
||||
proxy_pass http://localhost:8080/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Ssl on;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
}
|
||||
##### /phmyadmin off #####
|
||||
|
||||
root /home/<USER>/<DOMAIN_NAME>;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass_header Server;
|
||||
proxy_hide_header Server;
|
||||
}
|
||||
|
||||
# openpanel
|
||||
|
||||
@@ -2,7 +2,7 @@ server {
|
||||
listen <LISTEN_IP>;
|
||||
server_name <DOMAIN_NAME> www.<DOMAIN_NAME>;
|
||||
modsecurity on;
|
||||
modsecurity_rules_file /etc/nginx/modsec/main.conf;
|
||||
modsecurity_rules_file /usr/local/nginx/conf/modsecurity.conf;
|
||||
include snippets/error_pages.conf;
|
||||
access_log /var/log/nginx/domlogs/<DOMAIN_NAME>.log;
|
||||
include /etc/openpanel/openpanel/core/users/<USERNAME>/domains/<DOMAIN_NAME>-block_ips.conf;
|
||||
@@ -19,6 +19,8 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass_header Server;
|
||||
proxy_hide_header Server;
|
||||
}
|
||||
|
||||
# openpanel
|
||||
|
||||
@@ -1,35 +1,14 @@
|
||||
# openpanel
|
||||
location /openpanel {
|
||||
proxy_pass https://127.0.0.1:2083;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
return 301 https://localhost:2083;
|
||||
}
|
||||
|
||||
# openadmin
|
||||
location /openadmin {
|
||||
proxy_pass https://127.0.0.1:2087;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
return 301 https://localhost:2087;
|
||||
}
|
||||
|
||||
# webmail
|
||||
# roundcube
|
||||
location /webmail {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Autodiscover for Outlook & Thunderbird
|
||||
# https://github.com/stefanpejcic/Autodiscover/tree/main
|
||||
location /AutoDiscover {
|
||||
proxy_pass http://127.0.0.1:8000/autodiscover/autodiscover.xml;
|
||||
}
|
||||
location /mail {
|
||||
proxy_pass http://127.0.0.1:8000/mail/config-v1.1.xml;
|
||||
return 301 https://webmail.localhost/;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"schedule": "weekly",
|
||||
"retention": "4",
|
||||
"status": "off",
|
||||
"filters": ["files", "entrypoint", "apache-conf", "mysql-data", "mysql-conf", "php-versions", "crontab", "user-data", "core-users", "stats-users", "apache-ssl-conf", "domain-access-reports", "ssh", "timzeone"],
|
||||
"filters": ["files", "entrypoint", "apache-conf", "mysql-data", "mysql-conf", "php-versions", "crontab", "user-data", "core-users", "stats-users", "apache-ssl-conf", "domain-access-reports", "ssh", "timezone"],
|
||||
"id": "3"
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"OpenCLI Logs": "/var/log/openpanel/admin/opencli.log",
|
||||
"Certbot SSL Logs": "/var/log/letsencrypt/letsencrypt.log",
|
||||
"MySQL Logs": "/var/log/mysql.log",
|
||||
"FTP Logs": "/var/log/ftp.log",
|
||||
"CSF Deny Log": "/etc/csf/csf.deny",
|
||||
"AuthLog": "/var/log/auth.log",
|
||||
"DPKG Log": "/var/log/dpkg.log",
|
||||
|
||||
@@ -53,6 +53,12 @@
|
||||
"on_dashboard": true,
|
||||
"real_name": "openpanel_dns"
|
||||
},
|
||||
{
|
||||
"name": "SSL",
|
||||
"type": "docker",
|
||||
"on_dashboard": false,
|
||||
"real_name": "certbot"
|
||||
},
|
||||
{
|
||||
"name": "ConfigServer Firewall",
|
||||
"type": "system",
|
||||
|
||||
@@ -23,7 +23,7 @@ ns3=
|
||||
ns4=
|
||||
email=
|
||||
logout_url=
|
||||
enabled_modules=dns,favorites,phpmyadmin,ssh,crons,backups,wordpress,pm2,disk_usage,inodes,usage,terminal,services,webserver,fix_permissions,process_manager,ip_blocker,redis,memcached,login_history,activity,twofa,domains_visitors
|
||||
enabled_modules=dns,favorites,phpmyadmin,temporary_links,ssh,crons,backups,wordpress,pm2,disk_usage,inodes,usage,terminal,services,webserver,fix_permissions,process_manager,ip_blocker,redis,memcached,login_history,activity,twofa,domains_visitors
|
||||
|
||||
available_modules=malware_scan, elasticsearch
|
||||
|
||||
@@ -68,6 +68,7 @@ basic_auth=no
|
||||
basic_auth_username=
|
||||
basic_auth_password=
|
||||
screenshots=http://screenshots-api.openpanel.com/screenshot
|
||||
temporary_links=https://preview.openpanel.org/index.php
|
||||
|
||||
[SMTP]
|
||||
mail_server=
|
||||
@@ -84,9 +85,6 @@ max_ram=90
|
||||
max_cpu=95
|
||||
|
||||
|
||||
[PHPMYADMIN]
|
||||
pma_url=
|
||||
|
||||
[SECURITY]
|
||||
backups_encryption_key=
|
||||
|
||||
|
||||
1
configuration/php/ioncube.txt.EXAMPLE
Normal file
1
configuration/php/ioncube.txt.EXAMPLE
Normal file
@@ -0,0 +1 @@
|
||||
https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64.tar.gz
|
||||
86
integrations/cpanel-import/README.md
Normal file
86
integrations/cpanel-import/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# cPanel 2 OpenPanel user import
|
||||
Free OpenPanel module to import cPanel backup in OpenPanel
|
||||
|
||||
Maintained by [CodeWithJuber](https://github.com/CodeWithJuber)
|
||||
|
||||
## Features
|
||||
|
||||
Currently suported for import:
|
||||
|
||||
- files and folders
|
||||
- mysql databases, users and grants
|
||||
- domains
|
||||
- dns zones
|
||||
- php version
|
||||
- wp sites
|
||||
- cronjobs
|
||||
|
||||
todo:
|
||||
|
||||
- ftp accounts
|
||||
- email accounts
|
||||
- nodejs/python apps
|
||||
- postgresql
|
||||
- ssh keys
|
||||
- ssl certificates
|
||||
|
||||
Steps:
|
||||
|
||||
|
||||
# cPanel to OpenPanel Migration Script
|
||||
|
||||
This script automates the process of migrating a cPanel backup to OpenPanel server. It handles various cPanel backup formats and restores essential components of the user's account.
|
||||
|
||||
## Features
|
||||
|
||||
- Supports multiple cPanel backup formats (cpmove, full backup, tar.gz, tgz, tar, zip)
|
||||
- Restores user account details, domains, and hosting plan settings
|
||||
- Migrates websites, databases, domains, SSL certificates, and DNS zones
|
||||
- Handles PHP version settings and cron jobs
|
||||
- Restores SSH access and file permissions
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
1. Run the script with sudo privileges:
|
||||
|
||||
```
|
||||
git clone https://github.com/stefanpejcic/cPanel-to-OpenPanel
|
||||
```
|
||||
|
||||
```
|
||||
bash cPanel-to-OpenPanel/cp-import.sh --backup-location /path/to/cpanel_backup.file --plan-name "default_plan_nginx"
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
- `--backup-location`: Path to the cPanel backup file (required)
|
||||
- `--plan-name`: Name of the hosting plan in OpenPanel (required)
|
||||
|
||||
## Important Notes
|
||||
|
||||
- This script should be run on the OpenPanel server where you want to import the cPanel backup.
|
||||
- The script requires internet access to install dependencies if they are not already present.
|
||||
- Large backups may take a considerable amount of time to process.
|
||||
- Some manual configuration may be required after the migration, depending on the complexity of the cPanel account.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues:
|
||||
|
||||
1. Check the script's output for error messages.
|
||||
2. Verify that all prerequisites are met.
|
||||
3. Ensure you have sufficient disk space and system resources.
|
||||
4. Check the OpenPanel logs for any additional error information.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions to improve the script are welcome. Please feel free to submit issues or pull requests.
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This script is provided as-is, without any guarantees. Always test thoroughly in a non-production environment before using in production.
|
||||
1073
integrations/cpanel-import/cp-import.sh
Normal file
1073
integrations/cpanel-import/cp-import.sh
Normal file
File diff suppressed because it is too large
Load Diff
35
integrations/cpanel-import/cpanel_import.py
Normal file
35
integrations/cpanel-import/cpanel_import.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import os
|
||||
import json # will use for get to return data
|
||||
import socket
|
||||
from flask import Flask, Response, abort, render_template, request, send_file, g, jsonify, session, url_for, flash, redirect, get_flashed_messages
|
||||
import subprocess
|
||||
from app import app, is_license_valid, login_required_route
|
||||
from modules.helpers import get_all_plans, is_username_unique
|
||||
|
||||
@app.route('/import/cpanel', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def import_cpanel_whm_account():
|
||||
if request.method == 'POST':
|
||||
path = request.form.get('path')
|
||||
plan_name = request.form.get('plan_name')
|
||||
|
||||
if not path or not plan_name:
|
||||
flash('Both path to the cPanel backup file (.tar.gz) and plan name are required!', 'error')
|
||||
return redirect('/import/cpanel')
|
||||
try:
|
||||
file_name = os.path.basename(path)
|
||||
log_file_name = f"cpanel_import_log_{os.path.splitext(file_name)[0]}"
|
||||
log_file_path = f"/var/log/openpanel/admin/{log_file_name}.log"
|
||||
|
||||
# Run the subprocess command and redirect stdout and stderr to the log file
|
||||
with open(log_file_path, 'w') as log_file:
|
||||
subprocess.Popen(['opencli', 'user-import', 'cpanel', path, plan_name], stdout=log_file, stderr=log_file)
|
||||
|
||||
flash(f'Import started! To track the progress open the log file: {log_file_path}', 'success')
|
||||
except Exception as e:
|
||||
flash(f'An error occurred: {str(e)}', 'error')
|
||||
|
||||
return redirect('/import/cpanel')
|
||||
else:
|
||||
# on GET we will list the sessions in progress..
|
||||
return render_template('cpanel-import.html', title='Import cPanel account')
|
||||
47
integrations/cpanel-import/mysql/json_2_sql.py
Normal file
47
integrations/cpanel-import/mysql/json_2_sql.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import sys
|
||||
import re
|
||||
|
||||
def remove_localhost_lines_and_replace_grant_usage(input_path, output_path):
|
||||
try:
|
||||
with open(input_path, 'r') as infile:
|
||||
lines = infile.readlines()
|
||||
|
||||
with open(output_path, 'w') as outfile:
|
||||
for line in lines:
|
||||
if 'localhost' in line:
|
||||
continue
|
||||
|
||||
# Replace escaped underscores
|
||||
line = line.replace(r'\_', '_')
|
||||
|
||||
# Check if the line starts with "GRANT USAGE ON"
|
||||
if line.startswith('GRANT USAGE ON'):
|
||||
# Extract the username and password from the line using regex
|
||||
match = re.match(r"GRANT USAGE ON \*\.\* TO '([^']+)'@'([^']+)' IDENTIFIED BY PASSWORD '([^']+)'", line)
|
||||
if match:
|
||||
user = match.group(1)
|
||||
host = match.group(2)
|
||||
password = match.group(3)
|
||||
new_line = f"CREATE USER '{user}'@'{host}' IDENTIFIED WITH 'mysql_native_password' AS '{password}';\n"
|
||||
|
||||
outfile.write(new_line)
|
||||
else:
|
||||
# If regex doesn't match, write the original line
|
||||
outfile.write(line)
|
||||
else:
|
||||
outfile.write(line)
|
||||
|
||||
print(f"Processed file saved to {output_path}")
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Error: The file {input_path} was not found.")
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: python script.py <input_sql_file> <output_sql_file>")
|
||||
else:
|
||||
input_path = sys.argv[1]
|
||||
output_path = sys.argv[2]
|
||||
remove_localhost_lines_and_replace_grant_usage(input_path, output_path)
|
||||
19
integrations/cpanel-import/templates/cpanel-import.html
Normal file
19
integrations/cpanel-import/templates/cpanel-import.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div>
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||
{{ message }}
|
||||
<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
1
integrations/digitalocean/.Rbuildignore
Normal file
1
integrations/digitalocean/.Rbuildignore
Normal file
@@ -0,0 +1 @@
|
||||
^LICENSE\.md$
|
||||
1
integrations/digitalocean/.github/CODEOWNERS
vendored
Normal file
1
integrations/digitalocean/.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@marketplace-eng
|
||||
27
integrations/digitalocean/.github/CONTRIBUTING.md
vendored
Normal file
27
integrations/digitalocean/.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Contributing
|
||||
|
||||
We enthusiastically encourage contributions of all sorts to our repository, from correcting typos, to improving checks or adding new ones.
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
This section guides you through submitting an issue for the Marketplace Partners. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:.
|
||||
|
||||
When you are reporting an issue, please [include as many details as possible](#how-do-i-submit-a-good-bug-report).
|
||||
|
||||
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
|
||||
|
||||
#### Before Submitting An Issue
|
||||
* **Check the [current issues](https://github.com/digitalocean/marketplace-partners/issues)**.
|
||||
|
||||
#### How Do I Submit A (Good) Issue?
|
||||
|
||||
Issues are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue and provide the following information listed below.
|
||||
|
||||
Explain the problem and include additional details to help maintainers reproduce the problem:
|
||||
|
||||
* **Use a clear and descriptive title** for the issue to identify the problem.
|
||||
* **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, **don't just say what you did, but explain how you did it**.
|
||||
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
|
||||
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
|
||||
* **Explain which behavior you expected to see instead and why.**
|
||||
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem.
|
||||
43
integrations/digitalocean/.gitignore
vendored
Normal file
43
integrations/digitalocean/.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Compiled source #
|
||||
###################
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
|
||||
# Packages #
|
||||
############
|
||||
# it's better to unpack these files and commit the raw source
|
||||
# git has its own built in compression methods
|
||||
*.7z
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
|
||||
# Logs and databases #
|
||||
######################
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# App generated files #
|
||||
######################
|
||||
.vscode
|
||||
|
||||
.Rproj.user
|
||||
812
integrations/digitalocean/CHANGELOG.md
Normal file
812
integrations/digitalocean/CHANGELOG.md
Normal file
@@ -0,0 +1,812 @@
|
||||
## CHANGELOG
|
||||
|
||||
### Remove fabric and add support for Ubuntu 20.04
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 8 Mar 2021 09:50:00 +0000
|
||||
> Author: Mauricio Vargas (@pachamaltese)
|
||||
|
||||
- add support to image test script for Ubuntu 20.04
|
||||
- removes fabric because of problems with Python 3
|
||||
- updated documentation
|
||||
|
||||
|
||||
### Add Support for Debian 9
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 29 Mar 2019 18:06:36 +0000
|
||||
> Author: root (root@debian-s-1vcpu-1gb-nyc1-01.localdomain)
|
||||
> Committer: root (root@debian-s-1vcpu-1gb-nyc1-01.localdomain)
|
||||
|
||||
- add support to image test script for Debian
|
||||
- update README's
|
||||
- for more info on changelog hooks please ref: https://github.com/dOpensource/dsiprouter/tree/dev/resources/git
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Update build-an-image-fabric.md
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 25 Mar 2019 15:41:52 +0100
|
||||
> Author: Divyendu Singh (divyendu.z@gmail.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### IMG-535 - Ensure validation script exits with proper status codes
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 21 Mar 2019 13:19:37 -0500
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Add step to cleanup to clear root mailbox if any mail is present
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 21 Mar 2019 13:13:49 -0500
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### also allow CentOs convention for locked password on root
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 18 Mar 2019 18:40:25 -0400
|
||||
> Author: Ken Bingham (w@qrk.us)
|
||||
> Committer: Ken Bingham (w@qrk.us)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### img_check.sh: Print date that script is run
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 13 Mar 2019 16:03:30 -0400
|
||||
> Author: John Gannon (jgannon@digitalocean.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
Assists in debugging efforts that may come up between DO and Vendors, as well as in DO's listing submission process.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### build-an-image.md: doctl instructions added
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 13 Mar 2019 14:40:54 -0400
|
||||
> Author: John Gannon (jgannon@digitalocean.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### removed .backup file
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 11 Mar 2019 11:17:59 -0500
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Added Packer example and reorganized template folders
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 11 Mar 2019 11:16:37 -0500
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### update img_check.sh exclude lfd.log log
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 6 Mar 2019 23:25:48 +1000
|
||||
> Author: George Liu (eva2000@centminmod.com)
|
||||
> Committer: George Liu (eva2000@centminmod.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### img_check.sh centos 7 & csf firewall compatibility
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 6 Mar 2019 22:00:27 +1000
|
||||
> Author: George Liu (eva2000@centminmod.com)
|
||||
> Committer: George Liu (eva2000@centminmod.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### README.md: Link out to snapshots documentation
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 4 Mar 2019 16:38:06 -0500
|
||||
> Author: John Gannon (jgannon@digitalocean.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Update build-an-image-fabric.md
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 28 Feb 2019 12:03:14 -0500
|
||||
> Author: Rijk van Zanten (rijkvanzanten@me.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
Hey there! I ran into some versioning issues while working with the example project. I hope this note will make sure other people don't spend 30min messing around as I did 🙂
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Update 001_onboot
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 22 Feb 2019 14:29:46 -0600
|
||||
> Author: Ryan Quinn (ryan@digitalocean.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
Fix for typo reported by @kspearrin
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Fix for IMG-559 / Issue 16 - Fail if private key found
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 22 Feb 2019 08:16:47 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### remove shutdown func add exit to build func and add copy for user
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 15 Feb 2019 10:30:09 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### stop apt-get from prompting about config files when upgrading packages
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 15 Feb 2019 10:04:01 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### replace per-once with per-instance
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 15 Feb 2019 09:57:35 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Fix to change recommendation from per-once to per-instance on firstboot scripts
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 13 Feb 2019 13:14:51 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Ensure instance folder is removed on build system
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 12 Feb 2019 15:05:55 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Fix for permissions not being copied and adding noninteractive env var
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 12 Feb 2019 15:04:53 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### moved postfix install fix to run if uncommented before apt installs
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 12 Feb 2019 12:41:02 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### build-an-image.md: typo
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 4 Feb 2019 17:31:02 -0500
|
||||
> Author: John Gannon (jgannon@digitalocean.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Couple of extra fixes, emphasis on MOTD
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 1 Feb 2019 15:28:23 -0800
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Add updates and upgrades to cleanup example script for manual build + remove byoi_validation leftover
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 29 Jan 2019 14:28:20 -0800
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Delete BYOI version of the script
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 22 Jan 2019 16:49:31 -0500
|
||||
> Author: John Gannon (jgannon@digitalocean.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
Will be confusing for users and we don't plan to push anyone to BYOI in the near term. Can always pull it back into master later on.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Initial release
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected: v1.0
|
||||
> Date: Wed, 16 Jan 2019 15:46:02 -0500
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### removed .vscode directory
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 16 Jan 2019 14:38:55 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### typos *facepalm*
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 16 Jan 2019 15:35:56 -0500
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### repo README.md updates to just point to the new docs – gonna merge the branch shortly
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 16 Jan 2019 15:34:26 -0500
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### formatting typos
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 16 Jan 2019 15:28:42 -0500
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### tweaks to fabric doc
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 16 Jan 2019 15:26:53 -0500
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### revision of the manual image build documentation
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 16 Jan 2019 14:13:08 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### adding some simple img_check and snapshot instructions
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Wed, 16 Jan 2019 13:52:45 -0500
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### just getting my various draft changes into order for Ryan
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 15 Jan 2019 15:25:41 -0500
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### img_check.sh: list the uninstalled security packages
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 10 Jan 2019 16:15:45 -0500
|
||||
> Author: John Gannon (jgannon@digitalocean.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### removed backup file
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 7 Jan 2019 12:44:24 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### changed name from "example" to "template"
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 7 Jan 2019 12:43:21 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Adding simple fabric example
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Mon, 7 Jan 2019 12:40:08 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### fix a typo
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 3 Jan 2019 12:50:59 -0800
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### language pass, samples subfolder for various scripts
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 3 Jan 2019 12:49:42 -0800
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### tweaker
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 3 Jan 2019 12:42:34 -0800
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### prefix tweak
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 3 Jan 2019 12:37:58 -0800
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### tweaks
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 3 Jan 2019 11:57:44 -0800
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### moving docs around a bit
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 3 Jan 2019 11:17:55 -0800
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Fix for Issue 8 - script crashes if apt-cache is not present on Ubuntu
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 4 Dec 2018 09:02:39 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Starter doc
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 9 Nov 2018 10:19:00 -0800
|
||||
> Author: Nick Wade (nwade@digitalocean.com)
|
||||
> Committer: Nick Wade (nwade@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### update for IMG-533 (check for do-agent)
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 9 Nov 2018 11:02:09 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Print authorized keys file
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 9 Nov 2018 10:21:30 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### print contents of authorized keys file if detected
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Fri, 9 Nov 2018 10:16:53 -0600
|
||||
> Author: rquinn (ryan@digitalocean.com)
|
||||
> Committer: rquinn (ryan@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Spelling mistakes correction
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Sun, 21 Oct 2018 15:50:28 +0530
|
||||
> Author: akshaybengani789 (33260831+akshaybengani789@users.noreply.github.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### remove final undefined BYOI references
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 18 Oct 2018 15:11:05 -0700
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### update to version 0.1 and release tarball
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 18 Oct 2018 15:03:09 -0700
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### no message
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 18 Oct 2018 14:59:34 -0700
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### last heading fix
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 18 Oct 2018 14:45:56 -0700
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### headings skipped
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 18 Oct 2018 14:44:54 -0700
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### woops, heading space required
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 18 Oct 2018 14:43:18 -0700
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### A few typo and consistency changes, along with fixing the image asset path
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 18 Oct 2018 14:41:29 -0700
|
||||
> Author: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: Nick Wade (9014043+wadenick@users.noreply.github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Initial open sourcing
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Thu, 18 Oct 2018 11:11:17 -0700
|
||||
> Author: Nick Wade (nwade@digitalocean.com)
|
||||
> Committer: Nick Wade (nwade@digitalocean.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Update README.md
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 16 Oct 2018 13:39:36 -0700
|
||||
> Author: wadenick (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
### Initial commit
|
||||
|
||||
> Branches Affected: master
|
||||
> Tags Affected:
|
||||
> Date: Tue, 16 Oct 2018 13:20:47 -0700
|
||||
> Author: wadenick (9014043+wadenick@users.noreply.github.com)
|
||||
> Committer: GitHub (noreply@github.com)
|
||||
|
||||
|
||||
|
||||
---
|
||||
194
integrations/digitalocean/LICENSE.md
Normal file
194
integrations/digitalocean/LICENSE.md
Normal file
@@ -0,0 +1,194 @@
|
||||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### Terms and Conditions for use, reproduction, and distribution
|
||||
|
||||
#### 1. Definitions
|
||||
|
||||
“License” shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
“Licensor” shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
“Legal Entity” shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, “control” means **(i)** the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or **(iii)** beneficial ownership of such entity.
|
||||
|
||||
“You” (or “Your”) shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
“Source” form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
“Object” form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
“Work” shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
“Derivative Works” shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
“Contribution” shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
“submitted” means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as “Not a Contribution.”
|
||||
|
||||
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
#### 2. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
#### 3. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
#### 4. Redistribution
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
* **(b)** You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
#### 5. Submission of Contributions
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
#### 6. Trademarks
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
#### 7. Disclaimer of Warranty
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
#### 8. Limitation of Liability
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
#### 9. Accepting Warranty or Additional Liability
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
_END OF TERMS AND CONDITIONS_
|
||||
|
||||
### APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets `[]` replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same “printed page” as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
449
integrations/digitalocean/README.md
Normal file
449
integrations/digitalocean/README.md
Normal file
@@ -0,0 +1,449 @@
|
||||
# DigitalOcean Marketplace Partner Tools
|
||||
|
||||
[](LICENSE)
|
||||
[](http://makeapullrequest.com)
|
||||
|
||||
This repository contains resources for [DigitalOcean Marketplace](https://marketplace.digitalocean.com/) partners, like documentation on image requirements and creation, tools for image cleanup and validation, and templates for build automation.
|
||||
|
||||
## Getting Started creating your Kubernetes based 1-Click App
|
||||
|
||||
Please visit [our Kubernetes Marketplace repo](https://github.com/digitalocean/marketplace-kubernetes/blob/master/CONTRIBUTING.md) for instructions on how to create and submit your Kubernetes based 1-Click App.
|
||||
|
||||
Kubernetes 1-Click Apps can be submitted via our [Marketplace Kubernetes Github repo](https://github.com/digitalocean/marketplace-kubernetes).
|
||||
|
||||
## Getting Started creating your Droplet based 1-Click App
|
||||
|
||||
The overall process for creating an image that you can submit as a Droplet based 1-Click App is as follows:
|
||||
|
||||
1. Create and configure a build Droplet manually first to make sure your configuration works. You can create a build Droplet with any method, like the [control panel](https://cloud.digitalocean.com/), the [API](https://developers.digitalocean.com/), or command-line tools like [`doctl`](https://github.com/digitalocean/doctl). **We strongly encourage you to use a $6 Droplet as your build Droplet.** Using a $6 Droplet as your build Droplet will ensure that all Droplet types will be available for usage with your 1-Click App.
|
||||
|
||||
2. Clean up and validate the build Droplet with the provided scripts, `cleanup.sh` and `img_check.sh`. The scripts will check for and fix potential security concerns and verify that the image will be compatible with Marketplace.
|
||||
|
||||
3. Use Packer to create a fresh [snapshot](https://www.digitalocean.com/docs/images/snapshots/) of the image that you want to create. While there are several ways to create an image, we recommend using Packer as the most simple and consistent option.
|
||||
|
||||
4. Submit your final image to the Marketplace team for review. This can be made through [our Vendor Portal](https://cloud.digitalocean.com/vendorportal). If you've signed expressed interest in joining the Marketplace through [the form on this page](https://marketplace.digitalocean.com/vendors) but you've not received a login for the Vendor Portal, please reach out to one-clicks-team@digitalocean.com and we'll help you out.
|
||||
|
||||
<a name="dbaas-integration"></a>
|
||||
|
||||
5. **(Optional)** Integrate a DigitalOcean Managed Database into your 1-Click App</h4>
|
||||
|
||||
As a Vendor, you can offer a DigitalOcean Managed Database (DBaaS) to any DigitalOcean customer at the time they spin up your Droplet based 1-Click App. You can customize your app image to integrate with the managed database directly, or let your customers complete the configuration themselves after the 1-Click App boots up. This benefits your customers in terms of database scalability and ease of management, and reduces the burden of database support for you as a vendor.
|
||||
|
||||
To enable this option, use the checkboxes that are shown in the Vendor Portal related to enablement of managed databases. If you’ve checked at least 1 box, when a user attempts to create your 1-Click App, they’ll receive a prompt like this one:
|
||||
|
||||

|
||||
|
||||
When a user selects the Managed Database option, DigitalOcean handles the creation of the database cluster as well as the user's Droplet. The Droplet will have a `DATABASE_URL` environment variable configured including a database connection string, such as:
|
||||
|
||||
`postgresql://doadmin:<password>@dbaas-db-11111-do-user-1111111-1.b.db.ondigitalocean.com:25060/defaultdb?sslmode=require`
|
||||
|
||||
The user’s managed database configuration and credentials will be stored in `/root/.digitalocean_dbaas_credentials` in the following format.
|
||||
|
||||
```
|
||||
db_protocol=
|
||||
db_username=
|
||||
db_password=
|
||||
db_host=
|
||||
db_port=
|
||||
db_database=
|
||||
```
|
||||
|
||||
To disable this feature for any new users of your 1-Click App, simply visit the Vendor Portal to edit your 1-Click App, removing the checkboxes next to all database engines. Once you save the edits, customers will no longer be given the option to add a managed database. Note that existing customers who have already deployed a managed database in conjunction with your 1-Click App will not be affected by that removal, and their managed databases will continue to operate.
|
||||
|
||||
## Build Automation with Packer
|
||||
|
||||
[Packer](https://www.packer.io/intro) is a tool for creating images from a single source configuration. Using this Packer template reduces the entire process of creating, configuring, validating, and snapshotting a build Droplet to a single command:
|
||||
|
||||
```
|
||||
packer build marketplace-image.json
|
||||
```
|
||||
|
||||
By doing this, there is a reduced likelihood of having to submit an image multiple times as a result of falling in any of the next steps:
|
||||
|
||||
- Installing OS updates
|
||||
- Deleting bash history.
|
||||
- Removing log files and SSH keys from the root user
|
||||
- Enabling the firewall (i.e. ufw if you use Ubuntu)
|
||||
|
||||
This repository is itself a Packer template for a LAMP stack. You can modify this template to use as a starting point for your image. Note that not all of the scripts/files in this repository are strictly necessary, as this aim at covering a broad case of application.
|
||||
|
||||
## Usage
|
||||
|
||||
To run the LAMP stack in this template, you'll need to [install Packer](https://www.packer.io/intro/getting-started/install.html) and [create a DigitalOcean personal access token](https://docs.digitalocean.com/reference/api/create-personal-access-token/) and set it to the `DIGITALOCEAN_TOKEN` environment variable. Running `packer build marketplace-image.json` without any other modifications will create a build Droplet configured with LAMP, clean and verify it, then power it down and snapshot it.
|
||||
|
||||
To start adapting this template for your own image, you can customize some variables in `marketplace-image.json`:
|
||||
|
||||
* `apt_packages` lists the APT packages to install on the build Droplet.
|
||||
* `image_name` defines the name of the resulting snapshot, which by default is `marketplace-snapshot-` with a UNIX timestamp appended.
|
||||
|
||||
You can also modify these variables at runtime by using [the `-var` flag](https://www.packer.io/docs/templates/legacy_json_templates/user-variables).
|
||||
|
||||
Please see the [RStudio Server 1-Click Scripts](https://github.com/pachadotdev/rstudio-server-droplet) to see an example of Packer usage.
|
||||
|
||||
A successful run would look like this output:
|
||||
|
||||
```
|
||||
pacha@pop-os:~/github/marketplace-partners$ packer build marketplace-image.json
|
||||
digitalocean output will be in this color.
|
||||
|
||||
==> digitalocean: Creating temporary ssh key for droplet...
|
||||
==> digitalocean: Creating droplet...
|
||||
==> digitalocean: Waiting for droplet to become active...
|
||||
==> digitalocean: Using ssh communicator to connect: 165.227.211.66
|
||||
==> digitalocean: Waiting for SSH to become available...
|
||||
==> digitalocean: Connected to SSH!
|
||||
==> digitalocean: Provisioning with shell script: /tmp/packer-shell581341144
|
||||
digitalocean: .............................................................
|
||||
digitalocean: status: done
|
||||
==> digitalocean: Uploading files/etc/ => /etc/
|
||||
==> digitalocean: Uploading files/var/ => /var/
|
||||
==> digitalocean: Provisioning with shell script: /tmp/packer-shell079619818
|
||||
digitalocean:
|
||||
digitalocean: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
|
||||
|
||||
...
|
||||
|
||||
digitalocean: The following NEW packages will be installed:
|
||||
digitalocean: linux-headers-5.4.0-66 linux-headers-5.4.0-66-generic
|
||||
|
||||
digitalocean: The following packages will be upgraded:
|
||||
digitalocean: accountsservice alsa-ucm-conf apport apt apt-utils base-files bind9-dnsutils
|
||||
|
||||
... MANY MORE LINES OMITTED HERE ...
|
||||
|
||||
digitalocean: Distribution: Ubuntu
|
||||
digitalocean: Version: 20.04
|
||||
digitalocean:
|
||||
digitalocean: [PASS] Supported Operating System Detected: Ubuntu
|
||||
digitalocean: [PASS] Supported Release Detected: 20.04
|
||||
digitalocean: [PASS] Cloud-init is installed.
|
||||
digitalocean: [PASS] Firewall service (ufw) is active
|
||||
digitalocean:
|
||||
digitalocean: Updating apt package database to check for security updates, this may take a minute...
|
||||
digitalocean:
|
||||
digitalocean: [PASS] There are no pending security updates for this image.
|
||||
digitalocean:
|
||||
digitalocean:
|
||||
digitalocean: Checking for log files in /var/log
|
||||
digitalocean:
|
||||
digitalocean: [WARN] un-cleared log file, /var/log/auth.log found
|
||||
digitalocean: [WARN] un-cleared log file, /var/log/ufw.log found
|
||||
digitalocean:
|
||||
digitalocean:
|
||||
digitalocean: Checking all user-created accounts...
|
||||
digitalocean:
|
||||
digitalocean:
|
||||
digitalocean: Checking the root account...
|
||||
digitalocean: [PASS] User root has no password set.
|
||||
digitalocean: [ OK ] User root has no SSH keys present
|
||||
digitalocean: [PASS] root's Bash History appears to have been cleared
|
||||
digitalocean: [PASS] DigitalOcean Monitoring agent was not found
|
||||
digitalocean: [PASS] MongoDB is not installed
|
||||
digitalocean:
|
||||
digitalocean: ---------------------------------------------------------------------------------------------------
|
||||
digitalocean: Scan Complete.
|
||||
digitalocean: Some non-critical tests failed. Please review these items.
|
||||
digitalocean: ---------------------------------------------------------------------------------------------------
|
||||
digitalocean: 8 Tests PASSED
|
||||
digitalocean: 2 WARNINGS
|
||||
digitalocean: 0 Tests FAILED
|
||||
digitalocean: ---------------------------------------------------------------------------------------------------
|
||||
digitalocean: Please review all [WARN] items above and ensure they are intended or resolved. If you do not have a specific requirement, we recommend resolving these items before image submission
|
||||
digitalocean:
|
||||
==> digitalocean: Gracefully shutting down droplet...
|
||||
==> digitalocean: Creating snapshot: lemp-20-04-snapshot-1615212919
|
||||
==> digitalocean: Waiting for snapshot to complete...
|
||||
==> digitalocean: Destroying droplet...
|
||||
==> digitalocean: Deleting temporary ssh key...
|
||||
Build 'digitalocean' finished.
|
||||
```
|
||||
|
||||
See that this output has two acceptable warnings, but something like having ufw disabled or present SSH keys means we couldn't accept the image.
|
||||
|
||||
## Configuration Details
|
||||
|
||||
By using [Packer's DigitalOcean Builder](https://www.packer.io/docs/builders/digitalocean.html) to integrate with the [DigitalOcean API](https://developers.digitalocean.com/), this template fully automates Marketplace image creation.
|
||||
|
||||
This template uses Packer's [file provisioner](https://www.packer.io/docs/provisioners/file.html) to upload complete directories to the Droplet. The contents of `files/var/` will be uploaded to `/var/`. Likewise, the contents of `files/etc/` will be uploaded to `/etc/`. One important thing to note about the file provisioner, from Packer's docs:
|
||||
|
||||
> The destination directory must already exist. If you need to create it, use a shell provisioner just prior to the file provisioner in order to create the directory. If the destination directory does not exist, the file provisioner may succeed, but it will have undefined results.
|
||||
|
||||
This template also uses Packer's [shell provisioner](https://www.packer.io/docs/provisioners/shell.html) to run scripts from the `/scripts` directory and install APT packages using an inline task.
|
||||
|
||||
Learn more about using Packer in [the official Packer documentation](https://www.packer.io/docs/).
|
||||
|
||||
## Other Examples
|
||||
|
||||
We also use Packer to build some of the Marketplace 1-Click Apps that DigitalOcean maintains. You can see the source code for these scripts [in this repo.](https://github.com/digitalocean/droplet-1-clicks)
|
||||
|
||||
## Update your App Image via API
|
||||
|
||||
The Vendor API makes it possible to update existing droplet 1-click apps programmatically. You can use this to ensure your listing features the most recently released version by tying it into your existing CI/CD pipeline to push an update to DigitalOcean.
|
||||
|
||||
Not all listing information can be updated via the API. For any changes outside the parameters of this request, you will still need to visit the Vendor Portal.
|
||||
|
||||
To update your app via the API, send a PATCH request to https://api.digitalocean.com/api/v1/vendor-portal/apps/<app_id>/versions/<version>
|
||||
|
||||
App ID can be obtained from your app’s listing in Vendor Portal, via the URL. An invalid app ID will return a 404 Not Found error.
|
||||
|
||||
Apps in ‘pending’ or ‘in review’ state cannot be updated. Attempting to update an app in one of these states will return a 400 Bad Request error.
|
||||
|
||||
An authorization header with a bearer token is required to make the request. See [API documentation](https://docs.digitalocean.com/reference/api/api-reference/#section/Authentication) for more information on how to obtain this token.
|
||||
|
||||
### Request
|
||||
|
||||
**Authorizations**: bearer_auth (write)
|
||||
**Request Body schema**: application/json
|
||||
|
||||
#### Parameters:
|
||||
**imageId**
|
||||
_required_
|
||||
_integer_
|
||||
ID of the image to use for your app. The image must be a snapshot already uploaded to your DigitalOcean account, under the team you use to access the vendor portal. In addition to Packer, snapshots can be created via the API through [droplet actions](https://docs.digitalocean.com/reference/api/api-reference/#tag/Droplet-Actions). For additional information about manipulating snapshots via the API, view the [API documentation](https://docs.digitalocean.com/reference/api/api-reference/#tag/Snapshots) for snapshots.
|
||||
|
||||
**reasonForUpdate**
|
||||
_string_
|
||||
A brief description of the changes made which necessitate this update.
|
||||
|
||||
**version**
|
||||
_string_
|
||||
The version to mark this update as.
|
||||
|
||||
**osVersion**
|
||||
_string_
|
||||
The version of the operating system your app runs on. A null value will not overwrite an existing value, but a blank string will.
|
||||
|
||||
**softwareIncluded**
|
||||
_Array of Software (see below)_
|
||||
Software types and versions included with your app. A null value will not overwrite an existing value, but an empty array will.
|
||||
|
||||
|
||||
**Software**:
|
||||
|
||||
**name**
|
||||
_string_
|
||||
Name of this software. A null value will not overwrite an existing value, but a blank string will.
|
||||
|
||||
**version**
|
||||
_string_
|
||||
Version of this software in use by your app. A null value will not overwrite an existing value, but a blank string will.
|
||||
|
||||
**releaseNotes**
|
||||
_string_
|
||||
Any release notes to include alongside this software in your app’s listing. A null value will not overwrite an existing value, but a blank string will.
|
||||
|
||||
**website**
|
||||
_string_
|
||||
The website for this software, for further information. A null value will not overwrite an existing value, but a blank string will.
|
||||
|
||||
**licenseType**
|
||||
_string_
|
||||
The type of license this software uses. A null value will not overwrite an existing value, but a blank string will.
|
||||
|
||||
**licenseLink**
|
||||
_string_
|
||||
A link to details of the license, if applicable. A null value will not overwrite an existing value, but a blank string will.
|
||||
|
||||
|
||||
#### Example request:
|
||||
```
|
||||
curl -X PATCH \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $DIGITALOCEAN_API_TOKEN" \
|
||||
-d '{
|
||||
"reasonForUpdate": "example",
|
||||
"version": "3",
|
||||
"imageId": 413639,
|
||||
"softwareIncluded": [
|
||||
{
|
||||
"name": "Ubuntu Linux",
|
||||
"version": "22.04"
|
||||
}
|
||||
]
|
||||
}' \
|
||||
https://api.digitalocean.com/api/v1/vendor-portal/apps/$APP_ID/versions/$VERSION_ID
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
**200 - success**
|
||||
|
||||
Returns app data in response.
|
||||
|
||||
_Example response body:_
|
||||
```
|
||||
{
|
||||
"appId": "60089fc6d333037bffa70d9b",
|
||||
"name": "Example App ",
|
||||
"version": 4,
|
||||
"type": "droplet",
|
||||
...
|
||||
"status":
|
||||
{
|
||||
"value":"pending",
|
||||
"lastUpdated":1692302119556,
|
||||
"modifiedBy":"administrator",
|
||||
"reason":"example"
|
||||
},
|
||||
"emergencyContacts":
|
||||
[
|
||||
...
|
||||
],
|
||||
"customData":
|
||||
{
|
||||
"version": "3",
|
||||
"osVersion": "Ubuntu 20.04",
|
||||
"description": "app desc",
|
||||
"summary": "app summary",
|
||||
"imageLabel": "sample-20-04",
|
||||
"imageId": 417346,
|
||||
"imageName": "Sample on Ubuntu 20.04",
|
||||
"imageDescription": "Sample 3 on Ubuntu 20.04",
|
||||
"reasonForUpdate": "Sample",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**400 - Bad Request**
|
||||
- Image ID was missing from request, or
|
||||
- App in request was not a droplet 1-click, or
|
||||
- Attempted an update on an app in pending or in-review status
|
||||
|
||||
|
||||
**403 - Unauthorized**
|
||||
- Authentication token was invalid, or
|
||||
- App or image does not belong to requestor’s team
|
||||
|
||||
|
||||
**404 - Not Found**
|
||||
- App ID was not found or is invalid
|
||||
|
||||
|
||||
### Automating with Packer
|
||||
|
||||
If you are using Packer, you can add a set of post-processor actions to automatically submit your new image to update your app, via the [manifest](https://developer.hashicorp.com/packer/docs/post-processors/manifest) and [shell-local](https://developer.hashicorp.com/packer/docs/post-processors/shell-local) post-processors, such as:
|
||||
|
||||
```
|
||||
post-processors {
|
||||
post-processor "manifest" {
|
||||
output = "manifest.json"
|
||||
strip_path = true
|
||||
}
|
||||
|
||||
post-processor "shell-local" {
|
||||
inline = [ "sh mp-submit.sh" ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
where mp-submit.sh is
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
IMG_ID=$(jq '.builds[-1].artifact_id | split(":")[1] | tonumber' manifest.json)
|
||||
|
||||
curl -X PATCH -H "Content-Type: application/json" -H "Authorization: Bearer ${DIGITALOCEAN_API_TOKEN}" -d "{\"reasonForUpdate\": \"new version\", \"version\": \"${APP_VERSION}\", \"imageId\": ${IMG_ID}}" https://api.digitalocean.com/api/v1/vendor-portal/apps/${APP_ID}/versions/${APP_VERSION}
|
||||
```
|
||||
|
||||
You will need to set env variables DIGITALOCEAN_API_TOKEN, APP_VERSION, and APP_ID in your terminal to use this script. It uses jq to parse the manifest Packer creates for the snapshot ID, then uses cURL to submit it to the API endpoint.
|
||||
|
||||
|
||||
## Supported Operating Systems
|
||||
|
||||
To maintain compatibility with Marketplace tools and processes, we support a limited number of Linux distributions and releases for Marketplace images. These options provide either `deb`- or `rpm`-based packaging and will have security patches and updates for a reasonable time period.
|
||||
|
||||
We currently support the following OSes:
|
||||
|
||||
- Debian 9 (stretch)
|
||||
- Debian 10 (buster)
|
||||
- Ubuntu 24.04 (LTS)
|
||||
- Ubuntu 22.04 (LTS)
|
||||
- Ubuntu 20.04 (LTS)
|
||||
- Ubuntu 18.04 (LTS)
|
||||
- Ubuntu 16.04 (LTS)
|
||||
- CentOS 7.x
|
||||
- CentOS 8.x
|
||||
- CentOS Stream 8
|
||||
- CentOS Stream 9
|
||||
- AlmaLinux 8.x
|
||||
- AlmaLinux 9.x
|
||||
- Rocky Linux 8.x
|
||||
- Rocky Linux 9.x
|
||||
|
||||
All supported operating systems are available as base images to build on in the DigitalOcean cloud.
|
||||
|
||||
## Software Prerequisites
|
||||
|
||||
The following software packages are necessary for the initial configuration of new Droplets and to ensure connectivity:
|
||||
|
||||
- `cloud-init` 0.76 or higher (0.79 or higher recommended)
|
||||
- `openssh-server` (SFTP-enabled configuration recommended)
|
||||
|
||||
All of these packages are provided by default in the default DigitalOcean base images.
|
||||
|
||||
## Image Configuration
|
||||
|
||||
### Running Commands on First Boot
|
||||
|
||||
You can often pre-load much of what your image will need in your build system, but some setup (like setting database passwords or configuration that needs the Droplet's assigned IP address) will need to be run for each new Droplet created from your image.
|
||||
|
||||
You can create scripts that run on first boot using cloud-init. Droplets will attempt to run any scripts located in the `/var/lib/cloud/scripts/per-instance` directory when they're first created. Scripts in that directory are run in alphanumerical order, so we recommend using a number as the beginning of the file names (e.g. `01-example-script.sh`).
|
||||
|
||||
Make sure you can run the script from the command line successfully and that it has execute permissions.
|
||||
|
||||
### Running Commands on First Login
|
||||
|
||||
Some of your image setup may require information that you can't get automatically, like the domain name to use for a service. You may also need to run interactive third-party scripts, like LetsEncrypt's Certbot.
|
||||
|
||||
To run a script on the user's first login, we recommend adding a line to the root `.bashrc` file that runs the script and adding a line to the script that removes the line from the root `.bashrc` file.
|
||||
|
||||
More specifically, at the end of the script you want to run on first login, add the following line. For consistency, we recommend putting first login scripts in the `/opt/your_company_name` directory. Make sure the script has execute permissions.
|
||||
|
||||
```
|
||||
cp -f /etc/skel/.bashrc /root/.bashrc
|
||||
```
|
||||
|
||||
Then add a line to the end of `/root/.bashrc` that runs your script by specifying the full path to the script.
|
||||
|
||||
When the user first logs in, the system runs `.bashrc`, which will automatically run your script. The last line of the script overwrites the root `.bashrc` with the default `.bashrc` from the `/etc/skel` directory so the call to run your script no longer exists. Using this method, your script only runs once the first time the user logs in, but the file remains in the filesystem if they need to re-run or reference it later.
|
||||
|
||||
### Recommendations
|
||||
|
||||
* **Use the smallest suitable disk size**.
|
||||
|
||||
We don't support decreasing the size of a Droplet's disk because it poses data integrity issues. Building your image using the smallest disk size appropriate for your use case lets your users choose from the widest variety of Droplet plans.
|
||||
|
||||
* **Do not enable unnecessary DigitalOcean features on your build Droplet**.
|
||||
|
||||
By not enabling features like monitoring, IPv6, or private networking when you create your build Droplet, you retain more of your distribution's standard configuration, meaning you'll need to do less cleanup before you create the final image.
|
||||
|
||||
* **Install software updates from the distribution's repositories** before creating your final image.
|
||||
|
||||
This secures the system and can save your users time when they create new Droplets from your image.
|
||||
|
||||
* **Use official package repositories** or well-maintained third-party repositories whenever possible. Packages installed through other means may not provide a mechanism for applying timely security updates.
|
||||
|
||||
For official distribution packages, we recommend maintaining the `mirrors.digitalocean.com` mirrors, which are direct mirrors of the distribution's package archive. These mirrors are provided by default and provide faster downloads because the mirrors are stored within our infrastructure.
|
||||
|
||||
* **If you need to provide a password to your user, consider configuring it so that it is randomly generated at boot time** and explain to users via your Getting Started instructions how to access the password. Here's an example of how you can generate a high quality, 12 character password on a Linux Droplet, and store it in a file on the Droplet.
|
||||
|
||||
```sh
|
||||
gpg --gen-random --armor 2 12 > /root/.secrets.txt
|
||||
```
|
||||
|
||||
* **Add a message of the day (MOTD)**, which is text displayed when a user logs into their Droplet. We recommend writing an MOTD which introduces your image's features and points users to its documentation.
|
||||
|
||||
You can add an MOTD to your image by creating a text file in `/etc/update-motd.d`. Naming the file beginning with `99`, like `99-image-readme`, will display the MOTD as the last text the user sees before the login prompt.
|
||||
|
||||
## Contributing
|
||||
|
||||
We'd love to have your contribution to this project! [You can find more details here](https://github.com/digitalocean/marketplace-partners/blob/master/.github/CONTRIBUTING.md).
|
||||
|
||||
## Caveats
|
||||
|
||||
Avoid building architecture specific components into your 1-Click App, as your App may be run by DigitalOcean customers across a variety of operating systems and underlying hypervisors. You should use generic machine architecture to ensure consistent use across all infrastructure.
|
||||
|
||||
For example, avoid building ruby gems with native extensions as the underlying machine architecture may use flags that don't exist across all hypervisors.
|
||||
@@ -0,0 +1,95 @@
|
||||
# You may add here your
|
||||
# server {
|
||||
# ...
|
||||
# }
|
||||
# statements for each of your virtual hosts to this file
|
||||
|
||||
##
|
||||
# You should look at the following URL's in order to grasp a solid understanding
|
||||
# of Nginx configuration files in order to fully unleash the power of Nginx.
|
||||
# http://wiki.nginx.org/Pitfalls
|
||||
# http://wiki.nginx.org/QuickStart
|
||||
# http://wiki.nginx.org/Configuration
|
||||
#
|
||||
# Generally, you will want to move this file somewhere, and start with a clean
|
||||
# file but keep this around for reference. Or just disable in sites-enabled.
|
||||
#
|
||||
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
|
||||
##
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server ipv6only=on;
|
||||
|
||||
root /var/www/html;
|
||||
index index.php index.html index.htm;
|
||||
|
||||
# Make site accessible from http://localhost/
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
# First attempt to serve request as file, then
|
||||
# as directory, then fall back to displaying a 404.
|
||||
try_files $uri $uri/ =404;
|
||||
# Uncomment to enable naxsi on this location
|
||||
# include /etc/nginx/naxsi.rules
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
|
||||
}
|
||||
|
||||
# deny access to .htaccess files, if Apache's document root
|
||||
# concurs with nginx's one
|
||||
#
|
||||
#location ~ /\.ht {
|
||||
# deny all;
|
||||
#}
|
||||
}
|
||||
|
||||
|
||||
# another virtual host using mix of IP-, name-, and port-based configuration
|
||||
#
|
||||
#server {
|
||||
# listen 8000;
|
||||
# listen somename:8080;
|
||||
# server_name somename alias another.alias;
|
||||
# root html;
|
||||
# index index.html index.htm;
|
||||
#
|
||||
# location / {
|
||||
# try_files $uri $uri/ =404;
|
||||
# }
|
||||
#}
|
||||
|
||||
|
||||
# HTTPS server
|
||||
#
|
||||
#server {
|
||||
# listen 443;
|
||||
# server_name localhost;
|
||||
#
|
||||
# root html;
|
||||
# index index.html index.htm;
|
||||
#
|
||||
# ssl on;
|
||||
# ssl_certificate cert.pem;
|
||||
# ssl_certificate_key cert.key;
|
||||
#
|
||||
# ssl_session_timeout 5m;
|
||||
#
|
||||
# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
|
||||
# ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
|
||||
# ssl_prefer_server_ciphers on;
|
||||
#
|
||||
# location / {
|
||||
# try_files $uri $uri/ =404;
|
||||
# }
|
||||
#}
|
||||
27
integrations/digitalocean/files/etc/update-motd.d/99-one-click
Executable file
27
integrations/digitalocean/files/etc/update-motd.d/99-one-click
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Configured as part of the DigitalOcean 1-Click Image build process
|
||||
|
||||
myip=$(hostname -I | awk '{print$1}')
|
||||
cat <<EOF
|
||||
********************************************************************************
|
||||
|
||||
Welcome to DigitalOcean's 1-Click LEMP Droplet.
|
||||
To keep this Droplet secure, the UFW firewall is enabled.
|
||||
All ports are BLOCKED except 22 (SSH), 80 (HTTP), and 443 (HTTPS).
|
||||
|
||||
In a web browser, you can view:
|
||||
* The LEMP 1-Click Quickstart guide: https://do.co/2GOFe5J#start
|
||||
* Your LEMP website: http://$myip
|
||||
|
||||
On the server:
|
||||
* The default web root is located at /var/www/html
|
||||
* The MySQL root password is saved in /root/.digitalocean_password
|
||||
* Certbot is preinstalled. Run it to configure HTTPS. See
|
||||
https://do.co/2GOFe5J#enable-https for more detail.
|
||||
|
||||
For help and more information, visit https://do.co/2GOFe5J
|
||||
|
||||
********************************************************************************
|
||||
To delete this message of the day: rm -rf $(readlink -f ${0})
|
||||
EOF
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
#Generate Mysql root password.
|
||||
root_mysql_pass=$(openssl rand -hex 24)
|
||||
debian_sys_maint_mysql_pass=$(openssl rand -hex 24)
|
||||
|
||||
# Save the passwords
|
||||
cat > /root/.digitalocean_password <<EOM
|
||||
root_mysql_pass="${root_mysql_pass}"
|
||||
EOM
|
||||
|
||||
# Configure MySQL root password
|
||||
mysqladmin -u root -h localhost password ${root_mysql_pass}
|
||||
|
||||
mysql -uroot -p${root_mysql_pass} \
|
||||
-e "ALTER USER 'debian-sys-maint'@'localhost' IDENTIFIED BY '${debian_sys_maint_mysql_pass}'"
|
||||
|
||||
cat > /etc/mysql/debian.cnf <<EOM
|
||||
# Automatically generated for Debian scripts. DO NOT TOUCH!
|
||||
[client]
|
||||
host = localhost
|
||||
user = debian-sys-maint
|
||||
password = ${debian_sys_maint_mysql_pass}
|
||||
socket = /var/run/mysqld/mysqld.sock
|
||||
[mysql_upgrade]
|
||||
host = localhost
|
||||
user = debian-sys-maint
|
||||
password = ${debian_sys_maint_mysql_pass}
|
||||
socket = /var/run/mysqld/mysqld.sock
|
||||
EOM
|
||||
|
||||
myip=$(hostname -I | awk '{print$1}')
|
||||
sed -e "s|server_name localhost|servername ${myip}|g" \
|
||||
-i /etc/nginx/sites-available/00-digitalocean
|
||||
|
||||
systemctl restart nginx
|
||||
|
||||
# Remove the ssh force logout command
|
||||
sed -e '/Match User root/d' \
|
||||
-e '/.*ForceCommand.*droplet.*/d' \
|
||||
-i /etc/ssh/sshd_config
|
||||
|
||||
systemctl restart ssh
|
||||
103
integrations/digitalocean/files/var/www/html/index.html
Normal file
103
integrations/digitalocean/files/var/www/html/index.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: ProximaNova;
|
||||
font-size: 15px;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-radius: 3px;
|
||||
background-color: #0069ff;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 48px;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
width: 148px;
|
||||
}
|
||||
|
||||
.content {
|
||||
align-items: center;
|
||||
border: solid 2px #f1f1f1;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 32px auto;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
width: 960px;;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
color: #99999999;
|
||||
font-size: 13px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #676767;
|
||||
}
|
||||
|
||||
.empty-access {
|
||||
height: 220px;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 30px;
|
||||
color: #999999;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: ProximaNova;
|
||||
font-size: 21px;
|
||||
font-weight: 600;
|
||||
color: #444444;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<svg class="logo" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
|
||||
<g id="XMLID_17_">
|
||||
<g id="XMLID_18_">
|
||||
<g>
|
||||
<g id="XMLID_225_">
|
||||
<g id="XMLID_233_">
|
||||
<path id="XMLID_234_" fill="#0080FF" d="M15,30v-5.8c6.2,0,10.9-6.1,8.6-12.6c-0.9-2.4-2.8-4.3-5.2-5.2
|
||||
C11.9,4.1,5.8,8.8,5.8,15l0,0L0,15C0,5.2,9.5-2.5,19.8,0.7c4.5,1.4,8.1,5,9.5,9.5C32.5,20.5,24.8,30,15,30z"/>
|
||||
</g>
|
||||
<polygon id="XMLID_232_" fill="#0080FF" points="15,24.2 9.2,24.2 9.2,18.4 9.2,18.4 15,18.4 15,18.4"/>
|
||||
<polygon id="XMLID_228_" fill="#0080FF" points="9.2,28.7 4.8,28.7 4.8,28.7 4.8,24.2 9.2,24.2"/>
|
||||
<polygon id="XMLID_226_" fill="#0080FF" points="4.8,24.2 1,24.2 1,24.2 1,20.5 1,20.5 4.8,20.5 4.8,20.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="copyright">© 2018 DigitalOcean, LLC. All rights reserved.</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<svg id="svg" class="empty-access" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><defs><style>.cls-1,.cls-11,.cls-6,.cls-8{fill:#d7e9ff;}.cls-1{stroke:#d7e9ff;}.cls-1,.cls-10,.cls-13,.cls-14,.cls-15,.cls-16,.cls-17,.cls-18,.cls-2,.cls-3,.cls-4,.cls-6,.cls-7,.cls-9{stroke-linejoin:round;}.cls-1,.cls-13,.cls-14,.cls-2,.cls-3,.cls-6{stroke-width:1.6px;}.cls-1,.cls-5,.cls-7,.cls-8,.cls-9{fill-rule:evenodd;}.cls-12,.cls-2{fill:#8fbeff;}.cls-10,.cls-14,.cls-2,.cls-4,.cls-6,.cls-7{stroke:#8fbeff;}.cls-3{fill:#4894ff;}.cls-13,.cls-3{stroke:#4894ff;}.cls-13,.cls-14,.cls-15,.cls-16,.cls-18,.cls-4,.cls-7,.cls-9{fill:none;}.cls-15,.cls-16,.cls-18,.cls-4,.cls-6,.cls-7,.cls-9{stroke-linecap:round;}.cls-4{stroke-width:6px;}.cls-10,.cls-17,.cls-5{fill:#fff;}.cls-10,.cls-15,.cls-17,.cls-18,.cls-7,.cls-9{stroke-width:2px;}.cls-15,.cls-9{stroke:#0069ff;}.cls-16{stroke:#fff;stroke-width:1.6px;}.cls-17{stroke:#a5d1f7;}.cls-18{stroke:#1f8ced;}</style></defs><path class="cls-1" d="M148.23,190.5a5.31,5.31,0,0,1-5.31-5.31,5.31,5.31,0,0,1-5.31,5.31,5.31,5.31,0,0,1,5.31,5.31A5.31,5.31,0,0,1,148.23,190.5Z"/><path class="cls-1" d="M200.83,166.47a5.31,5.31,0,0,1-5.31-5.31,5.31,5.31,0,0,1-5.31,5.31,5.31,5.31,0,0,1,5.31,5.31A5.31,5.31,0,0,1,200.83,166.47Z"/><circle class="cls-2" cx="187.2" cy="213.07" r="1.11"/><circle class="cls-2" cx="199.83" cy="104.53" r="1.11"/><circle class="cls-3" cx="209.32" cy="188.1" r="1.11"/><circle class="cls-4" cx="166.27" cy="88.39" r="24.99" transform="translate(-0.6 175.63) rotate(-55.6)"/><polygon class="cls-5" points="121.26 188.1 161.43 156.45 176.42 107.55 144.73 85.85 104.56 117.51 89.57 166.4 121.26 188.1"/><rect class="cls-6" x="84.18" y="117.77" width="97.63" height="38.41" transform="translate(-55.16 169.32) rotate(-55.6)"/><polyline class="cls-7" points="161.43 156.45 176.42 107.55 144.73 85.85 104.56 117.51 89.57 166.4 121.26 188.1 153.98 162.32"/><path class="cls-8" d="M193,113a21.74,21.74,0,0,1-7-8.62,21.14,21.14,0,0,0-38.37,0,21.74,21.74,0,0,1-7,8.61,16.4,16.4,0,0,0-6.82,14.78,16,16,0,0,0,5,10.32,9.14,9.14,0,0,1,3,6.62h0a8.84,8.84,0,0,0,8.83,8.84l32.3,0a8.84,8.84,0,0,0,8.84-8.83h0a9.14,9.14,0,0,1,3-6.61,16,16,0,0,0,5-10.32A16.4,16.4,0,0,0,193,113Z"/><path class="cls-9" d="M181.81,98.41a21.14,21.14,0,0,0-34.21,6,21.74,21.74,0,0,1-7,8.61,16.4,16.4,0,0,0-6.82,14.78,16,16,0,0,0,5,10.32,9.14,9.14,0,0,1,3,6.62h0a8.84,8.84,0,0,0,8.83,8.84l32.3,0a8.84,8.84,0,0,0,8.84-8.83h0a9.14,9.14,0,0,1,3-6.61,16,16,0,0,0,5-10.32A16.4,16.4,0,0,0,193,113a20.5,20.5,0,0,1-3.61-3.32"/><circle class="cls-10" cx="166.78" cy="113.3" r="6.55"/><path class="cls-9" d="M166.78,106.75a6.55,6.55,0,0,1,0,13.1"/><path class="cls-5" d="M157.75,153.56l0,60.61a6.43,6.43,0,0,1,6.43,6.43h11.57l0-67h-18Z"/><rect class="cls-11" x="157.75" y="153.56" width="18" height="7.6"/><rect class="cls-11" x="164.16" y="153.56" width="5.14" height="67.04"/><rect class="cls-12" x="164.18" y="153.56" width="5.14" height="7.6"/><rect class="cls-13" x="164.16" y="153.56" width="5.14" height="67.04"/><line class="cls-14" x1="164.18" y1="161.16" x2="164.14" y2="220.6"/><path class="cls-9" d="M175.74,180.6l0-27h-18l0,60.61a6.43,6.43,0,0,1,6.43,6.43h11.57V208.51"/><line class="cls-15" x1="175.73" y1="203.51" x2="175.74" y2="185.6"/><path class="cls-4" d="M166.6,113.38a25,25,0,0,0,13.79-45.61"/><path class="cls-16" d="M166.6,113.38a25,25,0,0,0,13.79-45.61"/><path class="cls-16" d="M176.42,65.55a24.82,24.82,0,0,0-7-2"/><rect class="cls-17" x="152.27" y="130.97" width="29" height="8.5"/><line class="cls-18" x1="181.27" y1="130.97" x2="181.27" y2="139.47"/></svg>
|
||||
<h1 class="title">Please log into your Droplet with SSH to configure the LEMP installation.</h1>
|
||||
<p class="description">See the LEMP 1-Click Quickstart guide for detailed assistance.</p>
|
||||
<a class="button" href="https://do.co/2GOFe5J#start">Quickstart Guide</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
integrations/digitalocean/images/dbaas_1_click_offer.png
Normal file
BIN
integrations/digitalocean/images/dbaas_1_click_offer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 543 KiB |
72
integrations/digitalocean/marketplace-image.json
Normal file
72
integrations/digitalocean/marketplace-image.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"variables": {
|
||||
"do_token": "{{env `DIGITALOCEAN_TOKEN`}}",
|
||||
"image_name": "lemp-20-04-snapshot-{{timestamp}}",
|
||||
"apt_packages": "fail2ban mysql-server nginx php-apcu php-curl php-mysql php-fpm postfix python3-certbot-nginx software-properties-common",
|
||||
"application_name": "LEMP",
|
||||
"application_version": ""
|
||||
},
|
||||
"sensitive-variables": ["do_token"],
|
||||
"builders": [
|
||||
{
|
||||
"type": "digitalocean",
|
||||
"api_token": "{{user `do_token`}}",
|
||||
"image": "ubuntu-20-04-x64",
|
||||
"region": "nyc3",
|
||||
"size": "s-1vcpu-1gb",
|
||||
"ssh_username": "root",
|
||||
"snapshot_name": "{{user `image_name`}}"
|
||||
}
|
||||
],
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"cloud-init status --wait"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "files/etc/",
|
||||
"destination": "/etc/"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "files/var/",
|
||||
"destination": "/var/"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"environment_vars": [
|
||||
"DEBIAN_FRONTEND=noninteractive",
|
||||
"LC_ALL=C",
|
||||
"LANG=en_US.UTF-8",
|
||||
"LC_CTYPE=en_US.UTF-8"
|
||||
],
|
||||
"inline": [
|
||||
"apt -qqy update",
|
||||
"apt -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' full-upgrade",
|
||||
"apt -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' install {{user `apt_packages`}}",
|
||||
"apt-get -qqy clean"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"environment_vars": [
|
||||
"application_name={{user `application_name`}}",
|
||||
"application_version={{user `application_version`}}",
|
||||
"DEBIAN_FRONTEND=noninteractive",
|
||||
"LC_ALL=C",
|
||||
"LANG=en_US.UTF-8",
|
||||
"LC_CTYPE=en_US.UTF-8"
|
||||
],
|
||||
"scripts": [
|
||||
"scripts/01-lemp.sh",
|
||||
"scripts/02-ufw-nginx.sh",
|
||||
"scripts/03-force-ssh-logout.sh",
|
||||
"scripts/90-cleanup.sh",
|
||||
"scripts/99-img-check.sh"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
14
integrations/digitalocean/scripts/01-lemp.sh
Normal file
14
integrations/digitalocean/scripts/01-lemp.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DigitalOcean Marketplace Image Validation Tool
|
||||
# © 2021 DigitalOcean LLC.
|
||||
# This code is licensed under Apache 2.0 license (see LICENSE.md for details)
|
||||
|
||||
rm -rvf /etc/nginx/sites-enabled/default
|
||||
|
||||
ln -s /etc/nginx/sites-available/digitalocean \
|
||||
/etc/nginx/sites-enabled/digitalocean
|
||||
|
||||
rm -rf /var/www/html/index*debian.html
|
||||
|
||||
chown -R www-data: /var/www
|
||||
10
integrations/digitalocean/scripts/02-ufw-nginx.sh
Executable file
10
integrations/digitalocean/scripts/02-ufw-nginx.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
# DigitalOcean Marketplace Image Validation Tool
|
||||
# © 2021 DigitalOcean LLC.
|
||||
# This code is licensed under Apache 2.0 license (see LICENSE.md for details)
|
||||
|
||||
ufw limit ssh
|
||||
ufw allow 'Nginx Full'
|
||||
|
||||
ufw --force enable
|
||||
10
integrations/digitalocean/scripts/03-force-ssh-logout.sh
Executable file
10
integrations/digitalocean/scripts/03-force-ssh-logout.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
# DigitalOcean Marketplace Image Validation Tool
|
||||
# © 2021 DigitalOcean LLC.
|
||||
# This code is licensed under Apache 2.0 license (see LICENSE.md for details)
|
||||
|
||||
cat >> /etc/ssh/sshd_config <<EOM
|
||||
Match User root
|
||||
ForceCommand echo "Please wait while we get your droplet ready..."
|
||||
EOM
|
||||
49
integrations/digitalocean/scripts/90-cleanup.sh
Executable file
49
integrations/digitalocean/scripts/90-cleanup.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DigitalOcean Marketplace Image Validation Tool
|
||||
# © 2021 DigitalOcean LLC.
|
||||
# This code is licensed under Apache 2.0 license (see LICENSE.md for details)
|
||||
|
||||
set -o errexit
|
||||
|
||||
# Ensure /tmp exists and has the proper permissions before
|
||||
# checking for security updates
|
||||
# https://github.com/digitalocean/marketplace-partners/issues/94
|
||||
if [[ ! -d /tmp ]]; then
|
||||
mkdir /tmp
|
||||
fi
|
||||
chmod 1777 /tmp
|
||||
|
||||
if [ -n "$(command -v yum)" ]; then
|
||||
yum update -y
|
||||
yum clean all
|
||||
elif [ -n "$(command -v apt-get)" ]; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -y update
|
||||
apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes
|
||||
apt-get -y autoremove
|
||||
apt-get -y autoclean
|
||||
fi
|
||||
|
||||
rm -rf /tmp/* /var/tmp/*
|
||||
history -c
|
||||
cat /dev/null > /root/.bash_history
|
||||
unset HISTFILE
|
||||
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
|
||||
rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-????????
|
||||
rm -rf /var/lib/cloud/instances/*
|
||||
rm -f /root/.ssh/authorized_keys /etc/ssh/*key*
|
||||
touch /etc/ssh/revoked_keys
|
||||
chmod 600 /etc/ssh/revoked_keys
|
||||
|
||||
# Securely erase the unused portion of the filesystem
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m'
|
||||
printf "\n${GREEN}Writing zeros to the remaining disk space to securely
|
||||
erase the unused portion of the file system.
|
||||
Depending on your disk size this may take several minutes.
|
||||
The secure erase will complete successfully when you see:${NC}
|
||||
dd: writing to '/zerofile': No space left on device\n
|
||||
Beginning secure erase now\n"
|
||||
|
||||
dd if=/dev/zero of=/zerofile bs=4096 || rm /zerofile
|
||||
640
integrations/digitalocean/scripts/99-img-check.sh
Executable file
640
integrations/digitalocean/scripts/99-img-check.sh
Executable file
@@ -0,0 +1,640 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DigitalOcean Marketplace Image Validation Tool
|
||||
# © 2021-2022 DigitalOcean LLC.
|
||||
# This code is licensed under Apache 2.0 license (see LICENSE.md for details)
|
||||
|
||||
VERSION="v. 1.8.1"
|
||||
RUNDATE=$( date )
|
||||
|
||||
# Script should be run with SUDO
|
||||
if [ "$EUID" -ne 0 ]
|
||||
then echo "[Error] - This script must be run with sudo or as the root user."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATUS=0
|
||||
PASS=0
|
||||
WARN=0
|
||||
FAIL=0
|
||||
|
||||
# $1 == command to check for
|
||||
# returns: 0 == true, 1 == false
|
||||
cmdExists() {
|
||||
if command -v "$1" > /dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function getDistro {
|
||||
if [ -f /etc/os-release ]; then
|
||||
# freedesktop.org and systemd
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
OS=$NAME
|
||||
VER=$VERSION_ID
|
||||
elif type lsb_release >/dev/null 2>&1; then
|
||||
# linuxbase.org
|
||||
OS=$(lsb_release -si)
|
||||
VER=$(lsb_release -sr)
|
||||
elif [ -f /etc/lsb-release ]; then
|
||||
# For some versions of Debian/Ubuntu without lsb_release command
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/lsb-release
|
||||
OS=$DISTRIB_ID
|
||||
VER=$DISTRIB_RELEASE
|
||||
elif [ -f /etc/debian_version ]; then
|
||||
# Older Debian/Ubuntu/etc.
|
||||
OS=Debian
|
||||
VER=$(cat /etc/debian_version)
|
||||
elif [ -f /etc/SuSe-release ]; then
|
||||
# Older SuSE/etc.
|
||||
:
|
||||
elif [ -f /etc/redhat-release ]; then
|
||||
# Older Red Hat, CentOS, etc.
|
||||
VER=$(cut -d" " -f3 < /etc/redhat-release | cut -d "." -f1)
|
||||
d=$(cut -d" " -f1 < /etc/redhat-release | cut -d "." -f1)
|
||||
if [[ $d == "CentOS" ]]; then
|
||||
OS="CentOS Linux"
|
||||
fi
|
||||
else
|
||||
# Fall back to uname, e.g. "Linux <version>", also works for BSD, etc.
|
||||
OS=$(uname -s)
|
||||
VER=$(uname -r)
|
||||
fi
|
||||
}
|
||||
function loadPasswords {
|
||||
SHADOW=$(cat /etc/shadow)
|
||||
}
|
||||
|
||||
function checkAgent {
|
||||
# Check for the presence of the DO directory in the filesystem
|
||||
if [ -d /opt/digitalocean ];then
|
||||
echo -en "\e[41m[FAIL]\e[0m DigitalOcean directory detected.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
if [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]]; then
|
||||
echo "To uninstall the agent: 'sudo yum remove droplet-agent'"
|
||||
echo "To remove the DO directory: 'find /opt/digitalocean/ -type d -empty -delete'"
|
||||
elif [[ $OS == "Ubuntu" ]] || [[ $OS == "Debian" ]]; then
|
||||
echo "To uninstall the agent and remove the DO directory: 'sudo apt-get purge droplet-agent'"
|
||||
fi
|
||||
else
|
||||
echo -en "\e[32m[PASS]\e[0m DigitalOcean Monitoring agent was not found\n"
|
||||
((PASS++))
|
||||
fi
|
||||
}
|
||||
|
||||
function checkLogs {
|
||||
cp_ignore="/var/log/cpanel-install.log"
|
||||
echo -en "\nChecking for log files in /var/log\n\n"
|
||||
# Check if there are log archives or log files that have not been recently cleared.
|
||||
for f in /var/log/*-????????; do
|
||||
[[ -e $f ]] || break
|
||||
if [ "${f}" != "${cp_ignore}" ]; then
|
||||
echo -en "\e[93m[WARN]\e[0m Log archive ${f} found; Contents:\n"
|
||||
cat "${f}"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
for f in /var/log/*.[0-9];do
|
||||
[[ -e $f ]] || break
|
||||
echo -en "\e[93m[WARN]\e[0m Log archive ${f} found; Contents:\n"
|
||||
cat "${f}"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
done
|
||||
for f in /var/log/*.log; do
|
||||
[[ -e $f ]] || break
|
||||
if [[ "${f}" = '/var/log/lfd.log' && "$(grep -E -v '/var/log/messages has been reset| Watching /var/log/messages' "${f}" | wc -c)" -gt 50 ]]; then
|
||||
if [ "${f}" != "${cp_ignore}" ]; then
|
||||
echo -en "\e[93m[WARN]\e[0m un-cleared log file, ${f} found; Contents:\n"
|
||||
cat "${f}"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
elif [[ "${f}" != '/var/log/lfd.log' && "$(wc -c < "${f}")" -gt 50 ]]; then
|
||||
if [ "${f}" != "${cp_ignore}" ]; then
|
||||
echo -en "\e[93m[WARN]\e[0m un-cleared log file, ${f} found; Contents:\n"
|
||||
cat "${f}"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
function checkTMP {
|
||||
# Check the /tmp directory to ensure it is empty. Warn on any files found.
|
||||
return 1
|
||||
}
|
||||
function checkRoot {
|
||||
user="root"
|
||||
uhome="/root"
|
||||
for usr in $SHADOW
|
||||
do
|
||||
IFS=':' read -r -a u <<< "$usr"
|
||||
if [[ "${u[0]}" == "${user}" ]]; then
|
||||
if [[ ${u[1]} == "!" ]] || [[ ${u[1]} == "!!" ]] || [[ ${u[1]} == "*" ]]; then
|
||||
echo -en "\e[32m[PASS]\e[0m User ${user} has no password set.\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m User ${user} has a password set on their account.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ -d ${uhome}/ ]; then
|
||||
if [ -d ${uhome}/.ssh/ ]; then
|
||||
if ls ${uhome}/.ssh/*> /dev/null 2>&1; then
|
||||
for key in "${uhome}"/.ssh/*
|
||||
do
|
||||
if [ "${key}" == "${uhome}/.ssh/authorized_keys" ]; then
|
||||
|
||||
if [ "$(wc -c < "${key}")" -gt 50 ]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a populated authorized_keys file in \e[93m${key}\e[0m\n"
|
||||
akey=$(cat "${key}")
|
||||
echo "File Contents:"
|
||||
echo "$akey"
|
||||
echo "--------------"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
elif [ "${key}" == "${uhome}/.ssh/id_rsa" ]; then
|
||||
if [ "$(wc -c < "${key}")" -gt 0 ]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a private key file in \e[93m${key}\e[0m\n"
|
||||
akey=$(cat "${key}")
|
||||
echo "File Contents:"
|
||||
echo "$akey"
|
||||
echo "--------------"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
else
|
||||
echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has empty private key file in \e[93m${key}\e[0m\n"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
elif [ "${key}" != "${uhome}/.ssh/known_hosts" ]; then
|
||||
echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a file in their .ssh directory at \e[93m${key}\e[0m\n"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
else
|
||||
if [ "$(wc -c < "${key}")" -gt 50 ]; then
|
||||
echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a populated known_hosts file in \e[93m${key}\e[0m\n"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m has no SSH keys present\n"
|
||||
fi
|
||||
else
|
||||
echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have an .ssh directory\n"
|
||||
fi
|
||||
if [ -f /root/.bash_history ];then
|
||||
|
||||
BH_S=$(wc -c < /root/.bash_history)
|
||||
|
||||
if [[ $BH_S -lt 200 ]]; then
|
||||
echo -en "\e[32m[PASS]\e[0m ${user}'s Bash History appears to have been cleared\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m ${user}'s Bash History should be cleared to prevent sensitive information from leaking\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
|
||||
return 1;
|
||||
else
|
||||
echo -en "\e[32m[PASS]\e[0m The Root User's Bash History is not present\n"
|
||||
((PASS++))
|
||||
fi
|
||||
else
|
||||
echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have a directory in /home\n"
|
||||
fi
|
||||
echo -en "\n\n"
|
||||
return 1
|
||||
}
|
||||
|
||||
function checkUsers {
|
||||
# Check each user-created account
|
||||
awk -F: '$3 >= 1000 && $1 != "nobody" {print $1}' < /etc/passwd | while IFS= read -r user;
|
||||
do
|
||||
# Skip some other non-user system accounts
|
||||
if [[ $user == "centos" ]]; then
|
||||
:
|
||||
elif [[ $user == "nfsnobody" ]]; then
|
||||
:
|
||||
else
|
||||
echo -en "\nChecking user: ${user}...\n"
|
||||
for usr in $SHADOW
|
||||
do
|
||||
IFS=':' read -r -a u <<< "$usr"
|
||||
if [[ "${u[0]}" == "${user}" ]]; then
|
||||
if [[ ${u[1]} == "!" ]] || [[ ${u[1]} == "!!" ]] || [[ ${u[1]} == "*" ]]; then
|
||||
echo -en "\e[32m[PASS]\e[0m User ${user} has no password set.\n"
|
||||
# shellcheck disable=SC2030
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m User ${user} has a password set on their account. Only system users are allowed on the image.\n"
|
||||
# shellcheck disable=SC2030
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
#echo "User Found: ${user}"
|
||||
uhome="/home/${user}"
|
||||
if [ -d "${uhome}/" ]; then
|
||||
if [ -d "${uhome}/.ssh/" ]; then
|
||||
if ls "${uhome}/.ssh/*"> /dev/null 2>&1; then
|
||||
for key in "${uhome}"/.ssh/*
|
||||
do
|
||||
if [ "${key}" == "${uhome}/.ssh/authorized_keys" ]; then
|
||||
if [ "$(wc -c < "${key}")" -gt 50 ]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a populated authorized_keys file in \e[93m${key}\e[0m\n"
|
||||
akey=$(cat "${key}")
|
||||
echo "File Contents:"
|
||||
echo "$akey"
|
||||
echo "--------------"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
elif [ "${key}" == "${uhome}/.ssh/id_rsa" ]; then
|
||||
if [ "$(wc -c < "${key}")" -gt 0 ]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a private key file in \e[93m${key}\e[0m\n"
|
||||
akey=$(cat "${key}")
|
||||
echo "File Contents:"
|
||||
echo "$akey"
|
||||
echo "--------------"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
else
|
||||
echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has empty private key file in \e[93m${key}\e[0m\n"
|
||||
# shellcheck disable=SC2030
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
elif [ "${key}" != "${uhome}/.ssh/known_hosts" ]; then
|
||||
|
||||
echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a file in their .ssh directory named \e[93m${key}\e[0m\n"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
|
||||
else
|
||||
if [ "$(wc -c < "${key}")" -gt 50 ]; then
|
||||
echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a known_hosts file in \e[93m${key}\e[0m\n"
|
||||
((WARN++))
|
||||
if [[ $STATUS != 2 ]]; then
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
done
|
||||
else
|
||||
echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m has no SSH keys present\n"
|
||||
fi
|
||||
else
|
||||
echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have an .ssh directory\n"
|
||||
fi
|
||||
else
|
||||
echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have a directory in /home\n"
|
||||
fi
|
||||
|
||||
# Check for an uncleared .bash_history for this user
|
||||
if [ -f "${uhome}/.bash_history" ]; then
|
||||
BH_S=$(wc -c < "${uhome}/.bash_history")
|
||||
|
||||
if [[ $BH_S -lt 200 ]]; then
|
||||
echo -en "\e[32m[PASS]\e[0m ${user}'s Bash History appears to have been cleared\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m ${user}'s Bash History should be cleared to prevent sensitive information from leaking\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
|
||||
fi
|
||||
echo -en "\n\n"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
function checkFirewall {
|
||||
|
||||
if [[ $OS == "Ubuntu" ]]; then
|
||||
fw="ufw"
|
||||
ufwa=$(ufw status |head -1| sed -e "s/^Status:\ //")
|
||||
if [[ $ufwa == "active" ]]; then
|
||||
FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n"
|
||||
# shellcheck disable=SC2031
|
||||
((PASS++))
|
||||
else
|
||||
FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n"
|
||||
# shellcheck disable=SC2031
|
||||
((WARN++))
|
||||
fi
|
||||
elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]]; then
|
||||
if [ -f /usr/lib/systemd/system/csf.service ]; then
|
||||
fw="csf"
|
||||
if [[ $(systemctl status $fw >/dev/null 2>&1) ]]; then
|
||||
|
||||
FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n"
|
||||
((PASS++))
|
||||
elif cmdExists "firewall-cmd"; then
|
||||
if [[ $(systemctl is-active firewalld >/dev/null 2>&1 && echo 1 || echo 0) ]]; then
|
||||
FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n"
|
||||
((PASS++))
|
||||
else
|
||||
FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n"
|
||||
((WARN++))
|
||||
fi
|
||||
else
|
||||
FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n"
|
||||
((WARN++))
|
||||
fi
|
||||
else
|
||||
fw="firewalld"
|
||||
if [[ $(systemctl is-active firewalld >/dev/null 2>&1 && echo 1 || echo 0) ]]; then
|
||||
FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n"
|
||||
((PASS++))
|
||||
else
|
||||
FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n"
|
||||
((WARN++))
|
||||
fi
|
||||
fi
|
||||
elif [[ "$OS" =~ Debian.* ]]; then
|
||||
# user could be using a number of different services for managing their firewall
|
||||
# we will check some of the most common
|
||||
if cmdExists 'ufw'; then
|
||||
fw="ufw"
|
||||
ufwa=$(ufw status |head -1| sed -e "s/^Status:\ //")
|
||||
if [[ $ufwa == "active" ]]; then
|
||||
FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n"
|
||||
((PASS++))
|
||||
else
|
||||
FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n"
|
||||
((WARN++))
|
||||
fi
|
||||
elif cmdExists "firewall-cmd"; then
|
||||
fw="firewalld"
|
||||
if [[ $(systemctl is-active --quiet $fw) ]]; then
|
||||
FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n"
|
||||
((PASS++))
|
||||
else
|
||||
FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n"
|
||||
((WARN++))
|
||||
fi
|
||||
else
|
||||
# user could be using vanilla iptables, check if kernel module is loaded
|
||||
fw="iptables"
|
||||
if lsmod | grep -q '^ip_tables' 2>/dev/null; then
|
||||
FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n"
|
||||
((PASS++))
|
||||
else
|
||||
FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n"
|
||||
((WARN++))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
function checkUpdates {
|
||||
if [[ $OS == "Ubuntu" ]] || [[ "$OS" =~ Debian.* ]]; then
|
||||
# Ensure /tmp exists and has the proper permissions before
|
||||
# checking for security updates
|
||||
# https://github.com/digitalocean/marketplace-partners/issues/94
|
||||
if [[ ! -d /tmp ]]; then
|
||||
mkdir /tmp
|
||||
fi
|
||||
chmod 1777 /tmp
|
||||
|
||||
echo -en "\nUpdating apt package database to check for security updates, this may take a minute...\n\n"
|
||||
apt-get -y update > /dev/null
|
||||
|
||||
uc=$(apt-get --just-print upgrade | grep -i "security" -c)
|
||||
if [[ $uc -gt 0 ]]; then
|
||||
update_count=$(( uc / 2 ))
|
||||
else
|
||||
update_count=0
|
||||
fi
|
||||
|
||||
if [[ $update_count -gt 0 ]]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m There are ${update_count} security updates available for this image that have not been installed.\n"
|
||||
echo -en
|
||||
echo -en "Here is a list of the security updates that are not installed:\n"
|
||||
sleep 2
|
||||
apt-get --just-print upgrade | grep -i security | awk '{print $2}' | awk '!seen[$0]++'
|
||||
echo -en
|
||||
# shellcheck disable=SC2031
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
else
|
||||
echo -en "\e[32m[PASS]\e[0m There are no pending security updates for this image.\n\n"
|
||||
((PASS++))
|
||||
fi
|
||||
elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]]; then
|
||||
echo -en "\nChecking for available security updates, this may take a minute...\n\n"
|
||||
|
||||
update_count=$(yum check-update --security --quiet | wc -l)
|
||||
if [[ $update_count -gt 0 ]]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m There are ${update_count} security updates available for this image that have not been installed.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
else
|
||||
echo -en "\e[32m[PASS]\e[0m There are no pending security updates for this image.\n"
|
||||
((PASS++))
|
||||
fi
|
||||
else
|
||||
echo "Error encountered"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
return 1;
|
||||
}
|
||||
function checkCloudInit {
|
||||
|
||||
if hash cloud-init 2>/dev/null; then
|
||||
CI="\e[32m[PASS]\e[0m Cloud-init is installed.\n"
|
||||
((PASS++))
|
||||
else
|
||||
CI="\e[41m[FAIL]\e[0m No valid verison of cloud-init was found.\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }
|
||||
|
||||
|
||||
clear
|
||||
echo "DigitalOcean Marketplace Image Validation Tool ${VERSION}"
|
||||
echo "Executed on: ${RUNDATE}"
|
||||
echo "Checking local system for Marketplace compatibility..."
|
||||
|
||||
getDistro
|
||||
|
||||
echo -en "\n\e[1mDistribution:\e[0m ${OS}\n"
|
||||
echo -en "\e[1mVersion:\e[0m ${VER}\n\n"
|
||||
|
||||
ost=0
|
||||
osv=0
|
||||
|
||||
if [[ $OS == "Ubuntu" ]]; then
|
||||
ost=1
|
||||
if [[ $VER == "24.04" ]] || [[ $VER == "22.10" ]] || [[ $VER == "22.04" ]] || [[ $VER == "20.04" ]] || [[ $VER == "18.04" ]] || [[ $VER == "16.04" ]]; then
|
||||
osv=1
|
||||
fi
|
||||
|
||||
elif [[ "$OS" =~ Debian.* ]]; then
|
||||
ost=1
|
||||
case "$VER" in
|
||||
9)
|
||||
osv=1
|
||||
;;
|
||||
10)
|
||||
osv=1
|
||||
;;
|
||||
11)
|
||||
osv=1
|
||||
;;
|
||||
12)
|
||||
osv=1
|
||||
;;
|
||||
*)
|
||||
osv=2
|
||||
;;
|
||||
esac
|
||||
|
||||
elif [[ $OS == "CentOS Linux" ]]; then
|
||||
ost=1
|
||||
if [[ $VER == "8" ]]; then
|
||||
osv=1
|
||||
elif [[ $VER == "7" ]]; then
|
||||
osv=1
|
||||
elif [[ $VER == "6" ]]; then
|
||||
osv=1
|
||||
else
|
||||
osv=2
|
||||
fi
|
||||
elif [[ $OS == "CentOS Stream" ]]; then
|
||||
ost=1
|
||||
if [[ $VER == "8" ]]; then
|
||||
osv=1
|
||||
elif [[ $VER == "9" ]]; then
|
||||
osv=1
|
||||
else
|
||||
osv=2
|
||||
fi
|
||||
elif [[ $OS == "Rocky Linux" ]]; then
|
||||
ost=1
|
||||
if [[ $VER =~ 8\. ]] || [[ $VER =~ 9\. ]]; then
|
||||
osv=1
|
||||
else
|
||||
osv=2
|
||||
fi
|
||||
elif [[ $OS == "AlmaLinux" ]]; then
|
||||
ost=1
|
||||
if [[ "$VERSION" =~ 8.* ]] || [[ "$VERSION" =~ 9.* ]]; then
|
||||
osv=1
|
||||
else
|
||||
osv=2
|
||||
fi
|
||||
else
|
||||
ost=0
|
||||
fi
|
||||
|
||||
if [[ $ost == 1 ]]; then
|
||||
echo -en "\e[32m[PASS]\e[0m Supported Operating System Detected: ${OS}\n"
|
||||
((PASS++))
|
||||
else
|
||||
echo -en "\e[41m[FAIL]\e[0m ${OS} is not a supported Operating System\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
fi
|
||||
|
||||
if [[ $osv == 1 ]]; then
|
||||
echo -en "\e[32m[PASS]\e[0m Supported Release Detected: ${VER}\n"
|
||||
((PASS++))
|
||||
elif [[ $ost == 1 ]]; then
|
||||
echo -en "\e[41m[FAIL]\e[0m ${OS} ${VER} is not a supported Operating System Version\n"
|
||||
((FAIL++))
|
||||
STATUS=2
|
||||
else
|
||||
echo "Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checkCloudInit
|
||||
|
||||
echo -en "${CI}"
|
||||
|
||||
checkFirewall
|
||||
|
||||
echo -en "${FW_VER}"
|
||||
|
||||
checkUpdates
|
||||
|
||||
loadPasswords
|
||||
|
||||
checkLogs
|
||||
|
||||
echo -en "\n\nChecking all user-created accounts...\n"
|
||||
checkUsers
|
||||
|
||||
echo -en "\n\nChecking the root account...\n"
|
||||
checkRoot
|
||||
|
||||
checkAgent
|
||||
|
||||
|
||||
# Summary
|
||||
echo -en "\n\n---------------------------------------------------------------------------------------------------\n"
|
||||
|
||||
if [[ $STATUS == 0 ]]; then
|
||||
echo -en "Scan Complete.\n\e[32mAll Tests Passed!\e[0m\n"
|
||||
elif [[ $STATUS == 1 ]]; then
|
||||
echo -en "Scan Complete. \n\e[93mSome non-critical tests failed. Please review these items.\e[0m\e[0m\n"
|
||||
else
|
||||
echo -en "Scan Complete. \n\e[41mOne or more tests failed. Please review these items and re-test.\e[0m\n"
|
||||
fi
|
||||
echo "---------------------------------------------------------------------------------------------------"
|
||||
echo -en "\e[1m${PASS} Tests PASSED\e[0m\n"
|
||||
echo -en "\e[1m${WARN} WARNINGS\e[0m\n"
|
||||
echo -en "\e[1m${FAIL} Tests FAILED\e[0m\n"
|
||||
echo -en "---------------------------------------------------------------------------------------------------\n"
|
||||
|
||||
if [[ $STATUS == 0 ]]; then
|
||||
echo -en "We did not detect any issues with this image. Please be sure to manually ensure that all software installed on the base system is functional, secure and properly configured (or facilities for configuration on first-boot have been created).\n\n"
|
||||
exit 0
|
||||
elif [[ $STATUS == 1 ]]; then
|
||||
echo -en "Please review all [WARN] items above and ensure they are intended or resolved. If you do not have a specific requirement, we recommend resolving these items before image submission\n\n"
|
||||
exit 0
|
||||
else
|
||||
echo -en "Some critical tests failed. These items must be resolved and this scan re-run before you submit your image to the DigitalOcean Marketplace.\n\n"
|
||||
exit 1
|
||||
fi
|
||||
68
integrations/digitalocean/setup.sh
Normal file
68
integrations/digitalocean/setup.sh
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
|
||||
# should be run on clean droplet installation of ubuntu24
|
||||
# it will:
|
||||
#
|
||||
# install openpanel
|
||||
# delete admin account
|
||||
# setup script to create new admin on first ssh login
|
||||
# cleanup the image
|
||||
# trigger api to convert droplet to iso
|
||||
# add iso to existing marketplace item
|
||||
|
||||
|
||||
|
||||
# install latest panel!
|
||||
bash <(curl -sSL https://openpanel.org) --hostname=demo.openpanel.org
|
||||
|
||||
# remove admin accounts
|
||||
truncate -s 0 /etc/openpanel/openadmin/users.db
|
||||
|
||||
# cleanup logs
|
||||
rm -rf /etc/openpanel/admin/*
|
||||
rm -rf /etc/openpanel/user/*
|
||||
rm -rf /root/openpanel_install.log
|
||||
|
||||
# do image cleanup
|
||||
bash scripts/03-force-ssh-logout.sh
|
||||
bash scripts/03-force-ssh-logout.sh
|
||||
bash scripts/99-img-check.sh
|
||||
|
||||
|
||||
|
||||
# get droplet id
|
||||
droplet_id=$(curl http://169.254.169.254/metadata/v1/id)
|
||||
echo "DROPLET ID: $droplet_id"
|
||||
|
||||
|
||||
# fun stuff
|
||||
DO_API_TOKEN="TOKEN_HEREEEEEEEEEEEE"
|
||||
|
||||
|
||||
# create snapshot
|
||||
snapshot_name="openpanel-$(date +%Y-%m-%d-%H-%M-%S)"
|
||||
response=$(curl -X POST -H "Authorization: Bearer $DO_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type":"snapshot", "name":"'$snapshot_name'"}' \
|
||||
"https://api.digitalocean.com/v2/droplets/$droplet_id/actions")
|
||||
|
||||
action_id=$(echo "$response" | jq -r '.action.id')
|
||||
echo "Action ID: $action_id"
|
||||
|
||||
|
||||
status="in-progress"
|
||||
while [ "$status" == "in-progress" ]; do
|
||||
sleep 10
|
||||
response=$(curl -X GET -H "Authorization: Bearer $DO_API_TOKEN" \
|
||||
"https://api.digitalocean.com/v2/actions/$action_id")
|
||||
status=$(echo "$response" | jq -r '.action.status')
|
||||
echo "Snapshot status: $status"
|
||||
done
|
||||
|
||||
|
||||
snapshot_id=$(curl -X GET -H "Authorization: Bearer $DO_API_TOKEN" \
|
||||
"https://api.digitalocean.com/v2/droplets/$droplet_id/snapshots" | jq -r '.snapshots[0].id')
|
||||
|
||||
echo "Snapshot ID: $snapshot_id"
|
||||
|
||||
|
||||
538
integrations/fossbilling/OpenPanel.php
Normal file
538
integrations/fossbilling/OpenPanel.php
Normal file
@@ -0,0 +1,538 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright 2022-2024 FOSSBilling
|
||||
* Copyright 2011-2021 BoxBilling, Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0.
|
||||
*
|
||||
* @copyright FOSSBilling (https://www.fossbilling.org)
|
||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
|
||||
*/
|
||||
|
||||
use Random\RandomException;
|
||||
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
|
||||
|
||||
/**
|
||||
* OpenPanel API.
|
||||
*
|
||||
* @see https://dev.openpanel.co/api/
|
||||
*/
|
||||
class Server_Manager_Openpanel extends Server_Manager
|
||||
{
|
||||
/**
|
||||
* Returns the form configuration for the OpenPanel server manager.
|
||||
*
|
||||
* @return array the form configuration as an associative array
|
||||
*/
|
||||
public static function getForm(): array
|
||||
{
|
||||
return [
|
||||
'label' => 'OpenPanel',
|
||||
'form' => [
|
||||
'credentials' => [
|
||||
'fields' => [
|
||||
[
|
||||
'name' => 'username',
|
||||
'type' => 'text',
|
||||
'label' => 'Username',
|
||||
'placeholder' => 'Username used to connect to the server',
|
||||
'required' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'password',
|
||||
'type' => 'text',
|
||||
'label' => 'Password / Login Key',
|
||||
'placeholder' => 'Password or login key used to connect to the server',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the OpenPanel server manager.
|
||||
* Checks if the necessary configuration options are set and throws an exception if any are missing.
|
||||
*
|
||||
* @throws Server_Exception if any necessary configuration options are missing
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
if (empty($this->_config['host'])) {
|
||||
throw new Server_Exception('The ":server_manager" server manager is not fully configured. Please configure the :missing', [':server_manager' => 'OpenPanel', ':missing' => 'hostname'], 2001);
|
||||
}
|
||||
|
||||
if (empty($this->_config['username'])) {
|
||||
throw new Server_Exception('The ":server_manager" server manager is not fully configured. Please configure the :missing', [':server_manager' => 'OpenPanel', ':missing' => 'username'], 2001);
|
||||
}
|
||||
|
||||
if (empty($this->_config['password']) && empty($this->_config['accesshash'])) {
|
||||
throw new Server_Exception('The ":server_manager" server manager is not fully configured. Please configure the :missing', [':server_manager' => 'OpenPanel', ':missing' => 'authentication credentials'], 2001);
|
||||
}
|
||||
|
||||
// If port not set, use OpenPanel default.
|
||||
$this->_config['port'] = empty($this->_config['port']) ? '2087' : $this->_config['port'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function getAuthToken() {
|
||||
$apiProtocol = $this->_config['secure'] ? 'https://' : 'http://';
|
||||
$host = $this->_config['host'];
|
||||
$username = $this->_config['username'];
|
||||
$password = $this->_config['password'];
|
||||
$authEndpoint = $apiProtocol . $host . ':' . $this->getPort() . '/api/';
|
||||
|
||||
// Prepare cURL request to authenticate
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => $authEndpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode(array(
|
||||
'username' => $username,
|
||||
'password' => $password
|
||||
)),
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
"Content-Type: application/json"
|
||||
),
|
||||
));
|
||||
|
||||
// Execute cURL request to authenticate
|
||||
$response = curl_exec($curl);
|
||||
|
||||
// Check for errors
|
||||
if (curl_errno($curl)) {
|
||||
$token = false;
|
||||
$error = "cURL Error: " . curl_error($curl);
|
||||
} else {
|
||||
// Decode the response JSON to get the token
|
||||
$responseData = json_decode($response, true);
|
||||
$token = isset($responseData['access_token']) ? $responseData['access_token'] : false;
|
||||
$error = $token ? null : "Token not found in response";
|
||||
}
|
||||
|
||||
// Close cURL session
|
||||
curl_close($curl);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
function makeApiRequest($endpoint, $data = null, $method = 'GET') {
|
||||
$apiProtocol = $this->_config['secure'] ? 'https://' : 'http://';
|
||||
$host = $this->_config['host'];
|
||||
$baseUrl = $apiProtocol . $host . ':' . $this->getPort() . '/api/';
|
||||
|
||||
|
||||
|
||||
$url = $baseUrl . $endpoint;
|
||||
|
||||
$token = $this->getAuthToken();
|
||||
|
||||
if (!$token) {
|
||||
error_log("Failed to retrieve auth token");
|
||||
return false;
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 0,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $token
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
|
||||
$response = curl_exec($curl);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the login URL for a OpenPanel account.
|
||||
*
|
||||
* @param Server_Account|null $account The account for which to get the login URL. This parameter is currently not used.
|
||||
*
|
||||
* @return string the login URL
|
||||
*/
|
||||
public function getLoginUrl(Server_Account $account = null): string
|
||||
{
|
||||
$host = $this->_config['host'];
|
||||
$protocol = $this->_config['secure'] ? 'https://' : 'http://';
|
||||
|
||||
return $protocol . $host . ':2083';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the login URL for a OpenAdmin reseller account.
|
||||
*
|
||||
* @param Server_Account|null $account The account for which to get the login URL. This parameter is currently not used.
|
||||
*
|
||||
* @return string the login URL
|
||||
*/
|
||||
public function getResellerLoginUrl(Server_Account $account = null): string
|
||||
{
|
||||
$host = $this->_config['host'];
|
||||
$protocol = $this->_config['secure'] ? 'https://' : 'http://';
|
||||
$url = $protocol . $this->_config['host'] . ':' . $this->getPort() . '/api/';
|
||||
|
||||
return $protocol . $host . ':2087';
|
||||
}
|
||||
|
||||
# OpenAdmin can use custom port
|
||||
public function getPort(): int|string
|
||||
{
|
||||
$port = $this->_config['port'];
|
||||
|
||||
if (filter_var($port, FILTER_VALIDATE_INT) !== false && $port >= 0 && $port <= 65535) {
|
||||
return $this->_config['port'];
|
||||
} else {
|
||||
return 2087;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tests the connection to the OpenPanel server.
|
||||
* Sends a request to the OpenPanel server to get its version.
|
||||
*
|
||||
* @return true if the connection was successful
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function testConnection(): bool
|
||||
{
|
||||
|
||||
|
||||
$response = $this->makeApiRequest(null);
|
||||
$response = json_decode($response);
|
||||
|
||||
if($response->message === "API is working!") {
|
||||
return true;
|
||||
}
|
||||
throw new Server_Exception('Can\'t connect to the server');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a username for a new account on the OpenPanel server.
|
||||
* The username is generated based on the domain name, with some modifications to comply with OpenPanel's username restrictions.
|
||||
*
|
||||
* @param string $domain the domain name for which to generate a username
|
||||
*
|
||||
* @return string the generated username
|
||||
*
|
||||
* @throws RandomException if an error occurs during the generation of a random number
|
||||
*/
|
||||
public function generateUsername(string $domain): string
|
||||
{
|
||||
$processedDomain = strtolower(preg_replace('/[^A-Za-z0-9]/', '', $domain));
|
||||
$username = substr($processedDomain, 0, 7) . random_int(0, 9);
|
||||
|
||||
// OpenPanel doesn't allow usernames to start with "test", so replace it with a random string if it does (test3456 would then become something like a62f93456).
|
||||
if (str_starts_with($username, 'test')) {
|
||||
$username = substr_replace($username, 'a' . bin2hex(random_bytes(2)), 0, 5);
|
||||
}
|
||||
|
||||
return $username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes an account with the OpenPanel server.
|
||||
* Sends a request to the OpenPanel server to get the account's details and updates the Server_Account object accordingly.
|
||||
*
|
||||
* @param Server_Account $account the account to be synchronized
|
||||
*
|
||||
* @return Server_Account the updated account
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request, or if the account does not exist on the OpenPanel server
|
||||
*/
|
||||
public function synchronizeAccount(Server_Account $account)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new account on the OpenPanel server.
|
||||
* Sends a request to the OpenPanel server to create a new account with the details provided in the Server_Account object.
|
||||
* If the account is a reseller account, it also sets up the reseller and assigns the appropriate ACL list.
|
||||
*
|
||||
* @param Server_Account $account The account to be created. This object should contain all the necessary details for the new account.
|
||||
*
|
||||
* @return bool returns true if the account was successfully created, false otherwise
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request, or if the response from the OpenPanel server indicates an error
|
||||
*/
|
||||
public function createAccount(Server_Account $account)
|
||||
{
|
||||
$client = $account->getClient();
|
||||
$package = $account->getPackage();
|
||||
$this->getLog()->info('Creating account ' . $client->getUsername());
|
||||
$data = json_encode(array(
|
||||
"email" => $client->getEmail(),
|
||||
'username' => $account->getUsername(),
|
||||
'password' => $account->getPassword(),
|
||||
"plan_name" => $package->getName()
|
||||
|
||||
));
|
||||
|
||||
$response = $this->makeApiRequest("users" , $data, 'POST');
|
||||
$response = json_decode($response);
|
||||
|
||||
|
||||
if (!empty($response->success)) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
throw new Server_Exception('Error when creating ' . $client->getUsername() . ': ' . $response->error);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspends an account on the OpenPanel server.
|
||||
*
|
||||
* @param Server_Account $account the account to be suspended
|
||||
*
|
||||
* @return bool returns true if the account was successfully suspended
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function suspendAccount(Server_Account $account): bool
|
||||
{
|
||||
// Log the suspension
|
||||
$this->getLog()->info('Suspending account ' . $account->getUsername());
|
||||
|
||||
$client = $account->getClient();
|
||||
|
||||
$data = json_encode(array("action" => "suspend"));
|
||||
$response = $this->makeApiRequest("users/" . $account->getUsername() , $data, 'PATCH');
|
||||
$response = json_decode($response);
|
||||
|
||||
if ($response->success == 1 || $response->success == true ) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
throw new Server_Exception('Error when suspending ' . $client->getUsername() . ': ' . json_encode($response));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsuspends an account on the OpenPanel server.
|
||||
*
|
||||
* @param Server_Account $account the account to be unsuspended
|
||||
*
|
||||
* @return bool returns true if the account was successfully unsuspended
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function unsuspendAccount(Server_Account $account): bool
|
||||
{
|
||||
// Log the unsuspension
|
||||
$this->getLog()->info('Activating account ' . $account->getUsername());
|
||||
|
||||
$client = $account->getClient();
|
||||
|
||||
$data = json_encode(array("action" => "unsuspend"));
|
||||
$response = $this->makeApiRequest("users/". $account->getUsername() , $data, 'PATCH');
|
||||
$response = json_decode($response);
|
||||
|
||||
if ($response->success == 1 || $response->success == true ) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
throw new Server_Exception('Failed to unsuspend ' . $client->getUsername() . ': ' . $response->error);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels an account on the OpenPanel server.
|
||||
*
|
||||
* @param Server_Account $account the account to be cancelled
|
||||
*
|
||||
* @return bool returns true if the account was successfully cancelled
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function cancelAccount(Server_Account $account): bool
|
||||
{
|
||||
// Log the cancellation
|
||||
$this->getLog()->info('Canceling account ' . $account->getUsername());
|
||||
|
||||
$response = $this->makeApiRequest(endpoint: "users/". $account->getUsername(), method: 'DELETE');
|
||||
$response = json_decode($response);
|
||||
|
||||
if ($response->success) {
|
||||
return true;
|
||||
|
||||
}
|
||||
$client = $account->getClient();
|
||||
|
||||
|
||||
throw new Server_Exception('Failed to canceling ' . $client->getUsername() . ': ' . $response->error);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the package of an account on the OpenPanel server.
|
||||
*
|
||||
* @param Server_Account $account the account for which to change the package
|
||||
* @param Server_Package $package the new package
|
||||
*
|
||||
* @return bool returns true if the package was successfully changed
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function changeAccountPackage(Server_Account $account, Server_Package $package)
|
||||
{
|
||||
|
||||
// Log the package change
|
||||
$this->getLog()->info('Changing account ' . $account->getUsername() . ' package');
|
||||
$data = json_encode(array("plan_name" => $package->getName()));
|
||||
|
||||
$response = $this->makeApiRequest("users/". $account->getUsername(),$data , 'PUT');
|
||||
$response = json_decode($response);
|
||||
|
||||
if ($response->success) {
|
||||
return true;
|
||||
|
||||
}
|
||||
$client = $account->getClient();
|
||||
|
||||
|
||||
throw new Server_Exception('Failed to change package for user ' . $client->getUsername() . ' | Error: ' . $response->error);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the password of an account on the OpenPanel server.
|
||||
*
|
||||
* @param Server_Account $account the account for which to change the password
|
||||
* @param string $newPassword the new password
|
||||
*
|
||||
* @return bool returns true if the password was successfully changed
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function changeAccountPassword(Server_Account $account, string $newPassword)
|
||||
{
|
||||
// Log the password change
|
||||
$this->getLog()->info('Changing account ' . $account->getUsername() . ' password');
|
||||
|
||||
$data = json_encode(array("password" => $newPassword));
|
||||
|
||||
$response = $this->makeApiRequest("users/". $account->getUsername(),$data , 'PATCH');
|
||||
$response = json_decode($response);
|
||||
|
||||
if ($response->success) {
|
||||
return true;
|
||||
|
||||
}
|
||||
$client = $account->getClient();
|
||||
|
||||
|
||||
throw new Server_Exception('Failed to change package for user ' . $client->getUsername() . ' | Error: ' . $response->error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the username of an account on the OpenPanel server.
|
||||
*
|
||||
* @param Server_Account $account the account for which to change the username
|
||||
* @param string $newUsername the new username
|
||||
*
|
||||
* @return bool returns true if the username was successfully changed
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function changeAccountUsername(Server_Account $account, string $newUsername): bool
|
||||
{
|
||||
throw new Server_Exception('OpenPanel does not supporting changing username');
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the domain of an account on the OpenPanel server.
|
||||
*
|
||||
* @param Server_Account $account the account for which to change the domain
|
||||
* @param string $newDomain the new domain
|
||||
*
|
||||
* @return bool returns true if the domain was successfully changed
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function changeAccountDomain(Server_Account $account, string $newDomain): bool
|
||||
{
|
||||
throw new Server_Exception('OpenPanel does not supporting account domain');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the IP of an account on the OpenPanel server.
|
||||
*
|
||||
* @param Server_Account $account the account for which to change the IP
|
||||
* @param string $newIp the new IP
|
||||
*
|
||||
* @return bool returns true if the IP was successfully changed
|
||||
*
|
||||
* @throws Server_Exception if an error occurs during the request
|
||||
*/
|
||||
public function changeAccountIp(Server_Account $account, string $newIp): bool
|
||||
{
|
||||
throw new Server_Exception('OpenPanel does not supporting change account IP');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
41
integrations/fossbilling/README.md
Normal file
41
integrations/fossbilling/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# FOSSBilling-OpenPanel Server Manager
|
||||
|
||||
> [!NOTE]
|
||||
> Tested with [FOSSBilling](https://github.com/FOSSBilling/FOSSBilling) v0.6.22
|
||||
>
|
||||
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
- Download or git clone the OpenPanel.php file to your [FOSSBilling](https://github.com/FOSSBilling/FOSSBilling) installation at the following location: /library/Server/Manager
|
||||
|
||||
|
||||
## Features
|
||||
#### Server
|
||||
- ✅ Verify Connection
|
||||
- ✅ Create account
|
||||
- ✅ Cancel account
|
||||
- ✅ Suspend/Unsuspend account
|
||||
- ✅ Change account package
|
||||
- ✅ Change account password
|
||||
|
||||
#### Website Functions
|
||||
- ❌ Create Website (This will also create the user in OpenPanel)
|
||||
- ❌ Change Website Package
|
||||
- ❌ Suspend/Un-suspend Website
|
||||
|
||||
#### User Functions
|
||||
- ❌ Change User Password
|
||||
|
||||
#### Things The Don't Work Due To Lack Of API
|
||||
- ❌ Changing Account Username
|
||||
- ❌ Changing Account Domain
|
||||
- ❌ Synchronizing Accounts
|
||||
|
||||
## Important Notes
|
||||
|
||||
- This community-maintained package isn't affiliated with [FOSSBilling](https://github.com/FOSSBilling/FOSSBilling). Please report issues here rather than on the [FOSSBilling](https://github.com/FOSSBilling/FOSSBilling) repo.
|
||||
- Reseller support in the API is limited. It does support creating Reseller accounts, though the API doesn't seem to provide a way to get all the domains/users hosted by the Reseller to suspend/un-suspend them. I'm not really sure if this happens if the reseller's website get suspended.
|
||||
- For questions, concerns, or issues with this server manager, please open an issue on GitHub.
|
||||
- If [FOSSBilling](https://github.com/FOSSBilling/FOSSBilling) updates and breaks this server manager, report the issue here, and I'll update it to work with the latest version.
|
||||
2
integrations/paymenter.org/README.md
Normal file
2
integrations/paymenter.org/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# openpanel-paymenter.org
|
||||
OpenPanel account provisioning module for paymenter.org
|
||||
208
integrations/paymenter.org/openpanel.php
Normal file
208
integrations/paymenter.org/openpanel.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
################################################################################
|
||||
# Name: OpenPanel paymenter.org Module
|
||||
# Usage: https://openpanel.com/docs/articles/extensions/openpanel-and-paymenter.org/
|
||||
# Source: https://github.com/stefanpejcic/openpanel-paymenter.org
|
||||
# Author: Stefan Pejcic
|
||||
# Created: 09.10.2024
|
||||
# Last Modified: 09.10.2024
|
||||
# Company: openpanel.com
|
||||
# Copyright (c) Stefan Pejcic
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
################################################################################
|
||||
|
||||
namespace App\Extensions\Servers\OpenPanel;
|
||||
|
||||
use App\Classes\Extensions\Server;
|
||||
use App\Helpers\ExtensionHelper;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class OpenPanel extends Server
|
||||
{
|
||||
public function getMetadata()
|
||||
{
|
||||
return [
|
||||
'display_name' => 'OpenPanel',
|
||||
'version' => '1.0.0',
|
||||
'author' => 'Paymenter',
|
||||
'website' => 'https://paymenter.org',
|
||||
];
|
||||
}
|
||||
|
||||
public function getConfig()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'host',
|
||||
'friendlyName' => 'Url to OpenPanel server (with port)',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'username',
|
||||
'friendlyName' => 'Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'password',
|
||||
'friendlyName' => 'Password',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function getProductConfig($options)
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'packageName',
|
||||
'friendlyName' => 'Package Name',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'description' => 'Package Name for the OpenPanel server',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getUserConfig()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'domain',
|
||||
'friendlyName' => 'Domain',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'description' => 'Domain for the webhost',
|
||||
],
|
||||
[
|
||||
'name' => 'username',
|
||||
'friendlyName' => 'Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'description' => 'Username to login to the website',
|
||||
],
|
||||
[
|
||||
'name' => 'password',
|
||||
'friendlyName' => 'Password',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'description' => 'Password to login to the website',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function createServer($user, $params, $order, $product, $configurableOptions)
|
||||
{
|
||||
list($jwtToken, $error) = $this->getAuthToken();
|
||||
|
||||
if (!$jwtToken) {
|
||||
ExtensionHelper::error('OpenPanel', 'Failed to get authentication token: ' . $error);
|
||||
return;
|
||||
}
|
||||
|
||||
$createUserEndpoint = $this->getApiProtocol($params["host"]) . $params["host"] . ':2087/api/users';
|
||||
$response = Http::withToken($jwtToken)
|
||||
->post($createUserEndpoint, [
|
||||
'username' => $params['config']['username'],
|
||||
'password' => $params['config']['password'],
|
||||
'email' => $user->email,
|
||||
'plan_name' => $params['packageName'],
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
ExtensionHelper::error('OpenPanel', 'Failed to create server: ' . $response->body());
|
||||
}
|
||||
}
|
||||
|
||||
public function suspendServer($user, $params, $order, $product, $configurableOptions)
|
||||
{
|
||||
list($jwtToken, $error) = $this->getAuthToken();
|
||||
|
||||
if (!$jwtToken) {
|
||||
ExtensionHelper::error('OpenPanel', 'Failed to get authentication token: ' . $error);
|
||||
return;
|
||||
}
|
||||
|
||||
$suspendUserEndpoint = $this->getApiProtocol($params["host"]) . $params["host"] . ':2087/api/users/' . $params["config"]["username"] . '/suspend';
|
||||
$response = Http::withToken($jwtToken)->patch($suspendUserEndpoint);
|
||||
|
||||
if (!$response->successful()) {
|
||||
ExtensionHelper::error('OpenPanel', 'Failed to suspend server: ' . $response->body());
|
||||
}
|
||||
}
|
||||
|
||||
public function unsuspendServer($user, $params, $order, $product, $configurableOptions)
|
||||
{
|
||||
list($jwtToken, $error) = $this->getAuthToken();
|
||||
|
||||
if (!$jwtToken) {
|
||||
ExtensionHelper::error('OpenPanel', 'Failed to get authentication token: ' . $error);
|
||||
return;
|
||||
}
|
||||
|
||||
$unsuspendUserEndpoint = $this->getApiProtocol($params["host"]) . $params["host"] . ':2087/api/users/' . $params["config"]["username"] . '/unsuspend';
|
||||
$response = Http::withToken($jwtToken)->patch($unsuspendUserEndpoint);
|
||||
|
||||
if (!$response->successful()) {
|
||||
ExtensionHelper::error('OpenPanel', 'Failed to unsuspend server: ' . $response->body());
|
||||
}
|
||||
}
|
||||
|
||||
public function terminateServer($user, $params, $order, $product, $configurableOptions)
|
||||
{
|
||||
list($jwtToken, $error) = $this->getAuthToken();
|
||||
|
||||
if (!$jwtToken) {
|
||||
ExtensionHelper::error('OpenPanel', 'Failed to get authentication token: ' . $error);
|
||||
return;
|
||||
}
|
||||
|
||||
$deleteUserEndpoint = $this->getApiProtocol($params["host"]) . $params["host"] . ':2087/api/users/' . $params["config"]["username"];
|
||||
$response = Http::withToken($jwtToken)->delete($deleteUserEndpoint);
|
||||
|
||||
if (!$response->successful()) {
|
||||
ExtensionHelper::error('OpenPanel', 'Failed to terminate server: ' . $response->body());
|
||||
}
|
||||
}
|
||||
|
||||
private function getApiProtocol($hostname)
|
||||
{
|
||||
return filter_var($hostname, FILTER_VALIDATE_IP) === false ? 'https://' : 'http://';
|
||||
}
|
||||
|
||||
private function getAuthToken()
|
||||
{
|
||||
$authEndpoint = $this->getApiProtocol(ExtensionHelper::getConfig('OpenPanel', 'host')) . ExtensionHelper::getConfig('OpenPanel', 'host') . ':2087/api/auth';
|
||||
|
||||
$response = Http::post($authEndpoint, [
|
||||
'username' => ExtensionHelper::getConfig('OpenPanel', 'username'),
|
||||
'password' => ExtensionHelper::getConfig('OpenPanel', 'password'),
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
return [false, 'Failed to authenticate: ' . $response->body()];
|
||||
}
|
||||
|
||||
$responseData = $response->json();
|
||||
return [$responseData['access_token'] ?? false, 'Token not found in response'];
|
||||
}
|
||||
}
|
||||
36
integrations/whmcs/README.md
Normal file
36
integrations/whmcs/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# OpenPanel WHMCS Module 😎
|
||||
WHMCS module for [OpenPanel](https://openpanel.com)
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
- Server with OpenPanel Enterprise license
|
||||
- WHMCS
|
||||
|
||||
## Installation
|
||||
|
||||
1. Login to SSH for WHMCS server
|
||||
2. Navigate to `path_to_whmcs/modules/servers`
|
||||
3. Run this command to create a new folder and in it download the module:
|
||||
```bash
|
||||
git clone https://github.com/stefanpejcic/openpanel-whmcs-module.git openpanel
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
How to setup WHMCS and OpenPanel: https://openpanel.com/docs/articles/extensions/openpanel-and-whmcs/
|
||||
|
||||
|
||||
## Update
|
||||
|
||||
1. Login to SSH for WHMCS server
|
||||
2. Navigate to `path_to_whmcs/modules/servers/openpanel`
|
||||
3. Run this command to download newer files:
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
## Bug Reports
|
||||
|
||||
Report [new issue on github](https://github.com/stefanpejcic/openpanel-whmcs-module/issues/new/choose)
|
||||
|
||||
666
integrations/whmcs/openpanel.php
Normal file
666
integrations/whmcs/openpanel.php
Normal file
@@ -0,0 +1,666 @@
|
||||
<?php
|
||||
################################################################################
|
||||
# Name: OpenPanel WHMCS Module
|
||||
# Usage: https://openpanel.com/docs/articles/extensions/openpanel-and-whmcs/
|
||||
# Source: https://github.com/stefanpejcic/openpanel-whmcs-module
|
||||
# Author: Stefan Pejcic
|
||||
# Created: 01.05.2024
|
||||
# Last Modified: 09.10.2024
|
||||
# Company: openpanel.com
|
||||
# Copyright (c) Stefan Pejcic
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
################################################################################
|
||||
|
||||
|
||||
if (!defined("WHMCS")) {
|
||||
die("This file cannot be accessed directly");
|
||||
}
|
||||
|
||||
############### CORE STUFF ##################
|
||||
# BASIC AUTH, SHOULD BE REUSED IN ALL ROUTES
|
||||
function getApiProtocol($hostname) {
|
||||
return filter_var($hostname, FILTER_VALIDATE_IP) === false ? 'https://' : 'http://';
|
||||
}
|
||||
|
||||
|
||||
function getAuthToken($params) {
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$authEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/';
|
||||
|
||||
// Prepare cURL request to authenticate
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => $authEndpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode(array(
|
||||
'username' => $params["serverusername"],
|
||||
'password' => $params["serverpassword"]
|
||||
)),
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
"Content-Type: application/json"
|
||||
),
|
||||
));
|
||||
|
||||
// Execute cURL request to authenticate
|
||||
$response = curl_exec($curl);
|
||||
|
||||
// Check for errors
|
||||
if (curl_errno($curl)) {
|
||||
$token = false;
|
||||
$error = "cURL Error: " . curl_error($curl);
|
||||
} else {
|
||||
// Decode the response JSON to get the token
|
||||
$responseData = json_decode($response, true);
|
||||
$token = isset($responseData['access_token']) ? $responseData['access_token'] : false;
|
||||
$error = $token ? null : "Token not found in response";
|
||||
}
|
||||
|
||||
// Close cURL session
|
||||
curl_close($curl);
|
||||
|
||||
return array($token, $error);
|
||||
}
|
||||
|
||||
|
||||
function apiRequest($endpoint, $token, $data = null, $method = 'POST') {
|
||||
// Prepare cURL request
|
||||
$curl = curl_init();
|
||||
|
||||
// Set default cURL options
|
||||
$options = array(
|
||||
CURLOPT_URL => $endpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
"Authorization: Bearer " . $token,
|
||||
"Content-Type: application/json"
|
||||
),
|
||||
);
|
||||
|
||||
// Handle different HTTP methods
|
||||
switch ($method) {
|
||||
case 'POST':
|
||||
if ($data !== null) {
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_POSTFIELDS] = json_encode($data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'GET':
|
||||
$options[CURLOPT_CUSTOMREQUEST] = 'GET';
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
$options[CURLOPT_CUSTOMREQUEST] = 'PUT';
|
||||
if ($data !== null) {
|
||||
$options[CURLOPT_POSTFIELDS] = json_encode($data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CONNECT':
|
||||
$options[CURLOPT_CUSTOMREQUEST] = 'CONNECT';
|
||||
if ($data !== null) {
|
||||
$options[CURLOPT_POSTFIELDS] = json_encode($data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
$options[CURLOPT_CUSTOMREQUEST] = 'PATCH';
|
||||
if ($data !== null) {
|
||||
$options[CURLOPT_POSTFIELDS] = json_encode($data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
|
||||
if ($data !== null) {
|
||||
$options[CURLOPT_POSTFIELDS] = json_encode($data);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Handle unsupported methods
|
||||
throw new InvalidArgumentException("Unsupported method: $method");
|
||||
}
|
||||
|
||||
// Set the options for the cURL request
|
||||
curl_setopt_array($curl, $options);
|
||||
|
||||
// Execute cURL request
|
||||
$response = curl_exec($curl);
|
||||
|
||||
// Decode the response JSON
|
||||
$responseData = json_decode($response, true);
|
||||
|
||||
// Close cURL session
|
||||
curl_close($curl);
|
||||
|
||||
return $responseData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
############### USER ACTIONS ################
|
||||
# CREATE ACCOUNT
|
||||
function openpanel_CreateAccount($params) {
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
if (!$jwtToken) {
|
||||
return $error; // Return the error message as a plain string
|
||||
}
|
||||
|
||||
try {
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$createUserEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users';
|
||||
$packageId = $params['pid']; // Get the Product ID (Package ID)
|
||||
|
||||
// Query the database to get the package name
|
||||
$result = select_query("tblproducts", "name", array("id" => $packageId));
|
||||
$data = mysql_fetch_array($result);
|
||||
$packageName = $data['name']; // This is the package name
|
||||
|
||||
// Prepare data for user creation
|
||||
$userData = array(
|
||||
'username' => $params["username"],
|
||||
'password' => $params["password"],
|
||||
'email' => $params["clientsdetails"]["email"],
|
||||
'plan_name' => $packageName
|
||||
);
|
||||
|
||||
// Make API request to create user
|
||||
$response = apiRequest($createUserEndpoint, $jwtToken, $userData);
|
||||
|
||||
if (isset($response['success']) && $response['success'] === true) {
|
||||
return 'success';
|
||||
} else {
|
||||
return isset($response['error']) ? $response['error'] : 'An unknown error occurred.';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
__FUNCTION__,
|
||||
$params,
|
||||
$e->getMessage(),
|
||||
$e->getTraceAsString()
|
||||
);
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# TERMINATE ACCOUNT
|
||||
function openpanel_TerminateAccount($params) {
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
if (!$jwtToken) {
|
||||
return $error; // Return the error message as a plain string
|
||||
}
|
||||
|
||||
try {
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$userEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
|
||||
|
||||
// Step 1: Unsuspend the account if it's suspended
|
||||
try {
|
||||
$unsuspendData = array('action' => 'unsuspend');
|
||||
$unsuspendResponse = apiRequest($userEndpoint, $jwtToken, $unsuspendData, 'PATCH');
|
||||
|
||||
|
||||
} catch (Exception $e) {
|
||||
// If unsuspend fails, check if the account doesn't exist
|
||||
$errorMessage = $e->getMessage();
|
||||
if (strpos($errorMessage, 'not found') !== false || strpos($errorMessage, 'User') !== false) {
|
||||
// Account does not exist, return an error message
|
||||
return 'Error: Account "' . $params["username"] . '" does not exist and could not be deleted.';
|
||||
} else {
|
||||
return 'Failed to unsuspend account before termination: ' . $errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Now attempt to delete the account
|
||||
try {
|
||||
$response = apiRequest($userEndpoint, $jwtToken, null, 'DELETE');
|
||||
|
||||
|
||||
if (isset($response['success']) && $response['success'] === true) {
|
||||
return 'success';
|
||||
} else {
|
||||
return isset($response['error']) ? $response['error'] : 'An unknown error occurred during termination.';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Log the exception for the delete action
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
'TerminateAccount - Delete Exception',
|
||||
$params,
|
||||
$e->getMessage(),
|
||||
$e->getTraceAsString()
|
||||
);
|
||||
|
||||
// Handle exception during the delete action
|
||||
return 'Error during account termination: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
__FUNCTION__,
|
||||
$params,
|
||||
$e->getMessage(),
|
||||
$e->getTraceAsString()
|
||||
);
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# CHANGE PASSWORD FOR ACCOUNT
|
||||
function openpanel_ChangePassword($params) {
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
if (!$jwtToken) {
|
||||
return $error; // Return the error message as a plain string
|
||||
}
|
||||
|
||||
try {
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$changePasswordEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
|
||||
|
||||
// Prepare data for password change
|
||||
$passwordData = array('password' => $params["password"]);
|
||||
|
||||
// Make API request to change password for user
|
||||
$response = apiRequest($changePasswordEndpoint, $jwtToken, $passwordData, 'PATCH');
|
||||
|
||||
// Log the API request and response
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
'ChangePassword',
|
||||
$passwordData,
|
||||
$response
|
||||
);
|
||||
|
||||
// Check for success in the response
|
||||
if (isset($response['success']) && $response['success'] === true) {
|
||||
return 'success';
|
||||
} else {
|
||||
// Return the error message from the response or a default message
|
||||
return isset($response['error']) ? $response['error'] : 'An unknown error occurred during password change.';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Log the exception
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
'ChangePassword Exception',
|
||||
$params,
|
||||
$e->getMessage(),
|
||||
$e->getTraceAsString()
|
||||
);
|
||||
|
||||
// Return the exception message
|
||||
return 'Error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# SUSPEND ACCOUNT
|
||||
function openpanel_SuspendAccount($params) {
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
// If JWT token is not received, return error message
|
||||
if (!$jwtToken) {
|
||||
return json_encode(array("success" => false, "message" => $error));
|
||||
}
|
||||
|
||||
try {
|
||||
// Prepare the API endpoint for suspending the account
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$suspendAccountEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
|
||||
|
||||
// Prepare data for account suspension
|
||||
$suspendData = array('action' => 'suspend');
|
||||
|
||||
// Make the API request to suspend the account
|
||||
$response = apiRequest($suspendAccountEndpoint, $jwtToken, $suspendData, 'PATCH');
|
||||
|
||||
// Check the API response for success or failure
|
||||
if (isset($response['success']) && $response['success'] === true) {
|
||||
return 'success';
|
||||
} else {
|
||||
// Return the error message from the API response
|
||||
return isset($response['error']) ? $response['error'] : 'An unknown error occurred.';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Log the exception details
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
'SuspendAccount Exception',
|
||||
$params,
|
||||
$e->getMessage(),
|
||||
$e->getTraceAsString()
|
||||
);
|
||||
|
||||
// Return the exception message
|
||||
return 'Error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# UNSUSPEND ACCOUNT
|
||||
function openpanel_UnsuspendAccount($params) {
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
// If JWT token is not received, return error message
|
||||
if (!$jwtToken) {
|
||||
return json_encode(array("success" => false, "message" => $error));
|
||||
}
|
||||
|
||||
try {
|
||||
// Prepare the API endpoint to unsuspend the account
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$unsuspendAccountEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
|
||||
|
||||
// Prepare data for account unsuspension (if any)
|
||||
$unsuspendData = array('action' => 'unsuspend');
|
||||
|
||||
// Make the API request to unsuspend the account
|
||||
$response = apiRequest($unsuspendAccountEndpoint, $jwtToken, $unsuspendData, 'PATCH');
|
||||
|
||||
|
||||
// Check the API response for success or failure
|
||||
if (isset($response['success']) && $response['success'] === true) {
|
||||
return 'success';
|
||||
} else {
|
||||
// Return the error message from the API response
|
||||
return isset($response['error']) ? $response['error'] : 'An unknown error occurred.';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Log the exception details
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
'UnsuspendAccount Exception',
|
||||
$params,
|
||||
$e->getMessage(),
|
||||
$e->getTraceAsString()
|
||||
);
|
||||
|
||||
// Return the exception message
|
||||
return 'Error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# CHANGE PACKAGE (PLAN)
|
||||
function openpanel_ChangePackage($params) {
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
if (!$jwtToken) {
|
||||
return $error; // Return the error message as a plain string
|
||||
}
|
||||
|
||||
try {
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$changePlanEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
|
||||
|
||||
$packageId = $params['pid']; // Get the Product ID (Package ID)
|
||||
|
||||
// Query the database to get the package name
|
||||
$result = select_query("tblproducts", "name", array("id" => $packageId));
|
||||
$data = mysql_fetch_array($result);
|
||||
$packageName = $data['name']; // This is the package name
|
||||
|
||||
// Prepare data for changing plan
|
||||
$planData = array('plan_name' => $packageName);
|
||||
|
||||
// Make API request to change plan
|
||||
$response = apiRequest($changePlanEndpoint, $jwtToken, $planData, 'PUT');
|
||||
|
||||
// Log the API request and response
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
'ChangePackage',
|
||||
$planData,
|
||||
$response
|
||||
);
|
||||
|
||||
if (isset($response['success']) && $response['success'] === true) {
|
||||
return 'success';
|
||||
} else {
|
||||
// Return the error message from the response or a default message
|
||||
return isset($response['error']) ? $response['error'] : 'An unknown error occurred during package change.';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Log the exception
|
||||
logModuleCall(
|
||||
'openpanel',
|
||||
'ChangePackage Exception',
|
||||
$params,
|
||||
$e->getMessage(),
|
||||
$e->getTraceAsString()
|
||||
);
|
||||
|
||||
// Return the exception message
|
||||
return 'Error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
############### AUTOLOGIN LINKS ##############
|
||||
|
||||
# LOGIN FOR USERS ON FRONT
|
||||
function openpanel_ClientArea($params) {
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
if (!$jwtToken) {
|
||||
return '<p>Error: ' . $error . '</p>';
|
||||
}
|
||||
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$getLoginLinkEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
|
||||
|
||||
// Prepare data for login link generation
|
||||
$loginData = array();
|
||||
|
||||
// Make API request to get login link
|
||||
$response = apiRequest($getLoginLinkEndpoint, $jwtToken, $loginData, 'CONNECT');
|
||||
|
||||
if (isset($response["link"])) {
|
||||
$code = '<script>
|
||||
function loginOpenPanelButton() {
|
||||
var openpanel_btn = document.getElementById("loginLink");
|
||||
openpanel_btn.textContent = "Logging in...";
|
||||
document.getElementById("refreshMessage").style.display = "block";
|
||||
}
|
||||
</script>';
|
||||
$code .= '<a id="loginLink" class="btn btn-primary" href="' . $response["link"] . '" target="_blank" onclick="loginOpenPanelButton()">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 213.000000 215.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,215.000000) scale(0.100000,-0.100000)" fill="currentColor" stroke="none"><path d="M990 2071 c-39 -13 -141 -66 -248 -129 -53 -32 -176 -103 -272 -158 -206 -117 -276 -177 -306 -264 -17 -50 -19 -88 -19 -460 0 -476 0 -474 94 -568 55 -56 124 -98 604 -369 169 -95 256 -104 384 -37 104 54 532 303 608 353 76 50 126 113 147 184 8 30 12 160 12 447 0 395 -1 406 -22 461 -34 85 -98 138 -317 264 -104 59 -237 136 -295 170 -153 90 -194 107 -275 111 -38 2 -81 0 -95 -5z m205 -561 c66 -38 166 -95 223 -127 l102 -58 0 -262 c0 -262 0 -263 -22 -276 -13 -8 -52 -31 -88 -51 -36 -21 -126 -72 -200 -115 l-135 -78 -3 261 -3 261 -166 95 c-91 52 -190 109 -219 125 -30 17 -52 34 -51 39 3 9 424 256 437 255 3 0 59 -31 125 -69z"></path></g></svg> Login to OpenPanel
|
||||
</a>';
|
||||
$code .= '<p id="refreshMessage" style="display: none;">One-time login link has already been used, please refresh the page to login again.</p>';
|
||||
} else {
|
||||
$code = '<p>Error: Unable to generate login link for OpenPanel. Please try again later.</p>';
|
||||
if (isset($response["message"])) {
|
||||
$code .= '<p>Server Response: ' . htmlentities($response["message"]) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
|
||||
# LOGIN FROM admin/configservers.php
|
||||
function openpanel_AdminLink($params) {
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$adminLoginEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/login';
|
||||
|
||||
$code = '<form action="' . $adminLoginEndpoint . '" method="post" target="_blank">
|
||||
<input type="hidden" name="username" value="' . $params["serverusername"] . '" />
|
||||
<input type="hidden" name="password" value="' . $params["serverpassword"] . '" />
|
||||
<input type="submit" value="Login to OpenAdmin" />
|
||||
</form>';
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
# LOGIN FOR ADMINS FROM BACKEND
|
||||
function openpanel_LoginLink($params) {
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
if (!$jwtToken) {
|
||||
return '<p>Error: ' . $error . '</p>';
|
||||
}
|
||||
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$getLoginLinkEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
|
||||
|
||||
// Prepare data for login link generation
|
||||
$loginData = array();
|
||||
|
||||
// Make API request to get login link
|
||||
$response = apiRequest($getLoginLinkEndpoint, $jwtToken, $loginData, 'CONNECT');
|
||||
|
||||
if (isset($response["link"])) {
|
||||
$code = '<script>
|
||||
function loginOpenPanelButton() {
|
||||
var openpanel_btn = document.getElementById("loginLink");
|
||||
openpanel_btn.textContent = "Logging in...";
|
||||
document.getElementById("refreshMessage").style.display = "block";
|
||||
}
|
||||
</script>';
|
||||
$code .= '<a id="loginLink" class="btn btn-primary" href="' . $response["link"] . '" target="_blank" onclick="loginOpenPanelButton()">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 213.000000 215.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,215.000000) scale(0.100000,-0.100000)" fill="currentColor" stroke="none"><path d="M990 2071 c-39 -13 -141 -66 -248 -129 -53 -32 -176 -103 -272 -158 -206 -117 -276 -177 -306 -264 -17 -50 -19 -88 -19 -460 0 -476 0 -474 94 -568 55 -56 124 -98 604 -369 169 -95 256 -104 384 -37 104 54 532 303 608 353 76 50 126 113 147 184 8 30 12 160 12 447 0 395 -1 406 -22 461 -34 85 -98 138 -317 264 -104 59 -237 136 -295 170 -153 90 -194 107 -275 111 -38 2 -81 0 -95 -5z m205 -561 c66 -38 166 -95 223 -127 l102 -58 0 -262 c0 -262 0 -263 -22 -276 -13 -8 -52 -31 -88 -51 -36 -21 -126 -72 -200 -115 l-135 -78 -3 261 -3 261 -166 95 c-91 52 -190 109 -219 125 -30 17 -52 34 -51 39 3 9 424 256 437 255 3 0 59 -31 125 -69z"></path></g></svg> Login to OpenPanel
|
||||
</a>';
|
||||
$code .= '<p id="refreshMessage" style="display: none;">One-time login link has already been used, please refresh the page to login again.</p>';
|
||||
} else {
|
||||
// Log or print the response in case of error
|
||||
$code = '<p>Error: Unable to generate the login link. Please try again later.</p>';
|
||||
if (isset($response["message"])) {
|
||||
$code .= '<p>Server Response: ' . htmlentities($response["message"]) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
############### MAINTENANCE ################
|
||||
|
||||
|
||||
# TODO: GET USAGE FOR USERS!!!!!!!!
|
||||
function openpanel_UsageUpdate($params) {
|
||||
|
||||
# resposne should be formated like this:
|
||||
#{
|
||||
# "disk_usage": "1024 MB",
|
||||
# "disk_limit": "2048 MB",
|
||||
# "bandwidth_usage": "512 MB",
|
||||
# "bandwidth_limit": "1024 MB"
|
||||
#}
|
||||
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$authEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/';
|
||||
|
||||
// Authenticate and get JWT token
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
|
||||
if (!$jwtToken) {
|
||||
return json_encode(array(
|
||||
"success" => false,
|
||||
"message" => $error
|
||||
));
|
||||
}
|
||||
|
||||
// Prepare API endpoint for getting usage
|
||||
$getUsageEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/usage/';
|
||||
|
||||
// Prepare cURL request for getting usage
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => $getUsageEndpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => 'PATCH',
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
"Authorization: Bearer " . $jwtToken,
|
||||
"Content-Type: application/json"
|
||||
),
|
||||
));
|
||||
|
||||
// Execute cURL request for getting usage
|
||||
$response = curl_exec($curl);
|
||||
|
||||
// Check for errors
|
||||
if (curl_errno($curl)) {
|
||||
$result = json_encode(array(
|
||||
"success" => false,
|
||||
"message" => "cURL Error: " . curl_error($curl)
|
||||
));
|
||||
} else {
|
||||
// Decode the response JSON
|
||||
$usageData = json_decode($response, true);
|
||||
|
||||
// Loop through results and update database
|
||||
foreach ($usageData as $user => $values) {
|
||||
update_query("tblhosting", array(
|
||||
"diskusage" => $values['disk_usage'],
|
||||
"disklimit" => $values['disk_limit'],
|
||||
"lastupdate" => "now()"
|
||||
), array("server" => $params['serverid'], "username" => $user));
|
||||
}
|
||||
|
||||
$result = json_encode(array(
|
||||
"success" => true,
|
||||
"message" => "Usage updated successfully"
|
||||
));
|
||||
}
|
||||
|
||||
// Close cURL session
|
||||
curl_close($curl);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
22
integrations/whmcs/whmcs.json
Normal file
22
integrations/whmcs/whmcs.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"schema": "1.0",
|
||||
"type": "whmcs-servers",
|
||||
"name": "OpenPanel",
|
||||
"license": "proprietary",
|
||||
"category": "provisioning",
|
||||
"description": {
|
||||
"name": "OpenPanel",
|
||||
"tagline": "A Linux hosting control panel based on Docker.",
|
||||
"long": ""
|
||||
},
|
||||
"support": {
|
||||
"homepage": "https://openpanel.co/",
|
||||
"docs_url": "https://dev.openpanel.co/api"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "OPENPANEL",
|
||||
"homepage": "https:\/\/openpanel.co\/"
|
||||
}
|
||||
]
|
||||
}
|
||||
23
translations/README.md
Normal file
23
translations/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
### Available Translations
|
||||
|
||||
- [🇪🇸](https://community.openpanel.co/d/59-hablas-espanol-spanish-translation-for-openpanel)
|
||||
- [🇫🇷](https://community.openpanel.co/d/45-parles-tu-francais-french-translation-for-openpanel)
|
||||
- [🇩🇪](https://community.openpanel.co/d/25-sprichst-du-deutsch-german-translation-for-openpanel)
|
||||
- [🇹🇷](https://community.openpanel.co/d/31-turkce-konusuyor-musun-turkish-translation-for-openpanel)
|
||||
|
||||
|
||||
-----
|
||||
|
||||
## OpenPanel Translations
|
||||
OpenPanel needs you! If you would like to support OpenPanel, help us with the translation. 💖
|
||||
|
||||
|
||||
### How can you provide a translation for OpenPanel?
|
||||
|
||||
1. Fork [this repository](https://github.com/stefanpejcic/openpanel-translations/).
|
||||
2. Copy `en_us` to your locale e.g. `es_es`
|
||||
3. Translate the messages.pot file
|
||||
4. Send a pull request
|
||||
|
||||
|
||||
BIN
translations/de-de/messages.mo
Normal file
BIN
translations/de-de/messages.mo
Normal file
Binary file not shown.
5186
translations/de-de/messages.pot
Normal file
5186
translations/de-de/messages.pot
Normal file
File diff suppressed because it is too large
Load Diff
4941
translations/en-us/messages.pot
Normal file
4941
translations/en-us/messages.pot
Normal file
File diff suppressed because it is too large
Load Diff
5168
translations/es-es/messages.po
Normal file
5168
translations/es-es/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
5169
translations/es-es/messages.pot
Normal file
5169
translations/es-es/messages.pot
Normal file
File diff suppressed because it is too large
Load Diff
1
translations/fr-fr/.gitkeep
Normal file
1
translations/fr-fr/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
BIN
translations/fr-fr/messages.mo
Normal file
BIN
translations/fr-fr/messages.mo
Normal file
Binary file not shown.
5139
translations/fr-fr/messages.pot
Normal file
5139
translations/fr-fr/messages.pot
Normal file
File diff suppressed because it is too large
Load Diff
81
translations/install.sh
Normal file
81
translations/install.sh
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
|
||||
###
|
||||
# This script will help you install any desired locale
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# Installing single locale: bash <(curl -sSL https://raw.githubusercontent.com/stefanpejcic/openpanel-translations/main/install.sh) sr-sr
|
||||
#
|
||||
# Installing multiple locales at once: bash <(curl -sSL https://raw.githubusercontent.com/stefanpejcic/openpanel-translations/main/install.sh) sr-sr tr-tr
|
||||
#
|
||||
###
|
||||
|
||||
# might change in future
|
||||
github_repo="stefanpejcic/openpanel-translations"
|
||||
|
||||
# locales dir since OpenPanel v.0.2.1
|
||||
babel_translations="/etc/openpanel/openpanel/translations"
|
||||
|
||||
# at least 1 locale is needed
|
||||
if [ "$#" -lt 1 ]; then
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "jq command is required to parse JSON responses. Please install jq to use this feature."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Please provide at least one locale to the command, or a list"
|
||||
echo ""
|
||||
# list available locales from github repo
|
||||
echo "Available locales:"
|
||||
locales=$(curl -s "https://api.github.com/repos/$github_repo/contents" | jq -r '.[] | select(.type == "dir") | .name')
|
||||
echo "$locales"
|
||||
echo ""
|
||||
echo "Example for a single locale (DE): opencli locale de-de"
|
||||
echo ""
|
||||
echo "Example for multiple locales (DE & ES): opencli locale de-de es-es"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd /usr/local/panel
|
||||
|
||||
validate_locale() {
|
||||
# validate format (LL-LL or ll-ll)
|
||||
if [[ "$1" =~ ^[a-z]{2}-[a-z]{2}$ ]] || [[ "$1" =~ ^[A-Z]{2}-[A-Z]{2}$ ]]; then
|
||||
return 0 # ok
|
||||
else
|
||||
return 1 # not ok
|
||||
fi
|
||||
}
|
||||
|
||||
# Loop through each provided locale
|
||||
for locale in "$@"
|
||||
do
|
||||
# must be lowercase
|
||||
formatted_locale=$(echo "$locale" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
if validate_locale "$formatted_locale"; then
|
||||
# babel supports just 2 letters
|
||||
two_letter=$(echo "$locale" | cut -d'-' -f1 | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
echo "Creating directory for $formatted_locale locale.."
|
||||
echo ""
|
||||
mkdir -p $babel_translations/"$two_letter"/LC_MESSAGES/ &>/dev/null
|
||||
echo "Downloading $formatted_locale locale from https://raw.githubusercontent.com/$github_repo/main/$formatted_locale/messages.pot"
|
||||
wget -O $babel_translations/"$two_letter"/LC_MESSAGES/messages.pot "https://raw.githubusercontent.com/$github_repo/main/$formatted_locale/messages.pot" &>/dev/null
|
||||
pybabel init -i $babel_translations/$two_letter/LC_MESSAGES/messages.pot -d $babel_translations -l "$two_letter" &>/dev/null
|
||||
echo ""
|
||||
else
|
||||
echo "Invalid locale format: $locale. Skipping."
|
||||
fi
|
||||
done
|
||||
|
||||
# Do this only once
|
||||
|
||||
echo "Compiling .mo files for all available locales in $babel_translations directory.."
|
||||
pybabel compile -f -d $babel_translations &>/dev/null
|
||||
echo "Restarting OpenPanel to apply translations.."
|
||||
docker restart openpanel &>/dev/null
|
||||
echo "DONE"
|
||||
BIN
translations/tr-tr/messages.mo
Normal file
BIN
translations/tr-tr/messages.mo
Normal file
Binary file not shown.
5135
translations/tr-tr/messages.pot
Normal file
5135
translations/tr-tr/messages.pot
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user