mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
Auto-commit on 2024-12-03 10:35:31 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
|
||||
@@ -1,6 +1,6 @@
|
||||
$TTL 1h
|
||||
@ IN SOA {ns1}. {ns2}. (
|
||||
2024110501 ; Serial number
|
||||
2024120301 ; Serial number
|
||||
1h ; Refresh interval
|
||||
15m ; Retry interval
|
||||
1w ; Expire interval
|
||||
|
||||
@@ -70,6 +70,8 @@ services:
|
||||
- /etc/openpanel/openpanel/conf/knowledge_base_articles.json:/etc/openpanel/openpanel/conf/knowledge_base_articles.json
|
||||
# localization
|
||||
- /etc/openpanel/openpanel/translations/:/etc/openpanel/openpanel/translations/
|
||||
# emails
|
||||
- /usr/local/mail/openmail/:/usr/local/mail/openmail/
|
||||
network_mode: host
|
||||
mem_limit: 1g
|
||||
cpus: 1.0
|
||||
|
||||
30
configuration/docker/compose/ttyd-compose.yml
Normal file
30
configuration/docker/compose/ttyd-compose.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
services:
|
||||
# WEB TERMINAL FOR OPENADMIN
|
||||
ttyd:
|
||||
image: tsl0922/ttyd
|
||||
container_name: ttyd
|
||||
#ports:
|
||||
# - "7681:7681"
|
||||
network_mode: "host"
|
||||
command: ttyd -o -W bash
|
||||
restart: no
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- openadmin_mysql:/var/lib/mysql
|
||||
- /etc/:/etc/
|
||||
- /home/:/home/
|
||||
- /var/:/var/
|
||||
- /root/:/root/
|
||||
- /usr/local/bin/opencli:/usr/local/bin/opencli
|
||||
- /usr/local/csf:/usr/local/csf
|
||||
- /usr/sbin/:/usr/sbin/
|
||||
- /usr/bin/docker:/usr/bin/docker
|
||||
- /usr/bin/awk:/usr/bin/awk
|
||||
- /usr/local/admin/:/usr/local/admin/
|
||||
mem_limit: 1g
|
||||
cpus: 1.0
|
||||
oom_kill_disable: true
|
||||
privileged: true
|
||||
82
configuration/nginx/vhosts/docker_varnish_apache_domain.conf
Normal file
82
configuration/nginx/vhosts/docker_varnish_apache_domain.conf
Normal file
@@ -0,0 +1,82 @@
|
||||
<VirtualHost *:8080>
|
||||
ServerName <DOMAIN_NAME>
|
||||
ServerAlias www.<DOMAIN_NAME>
|
||||
DocumentRoot /home/<USER>/<DOMAIN_NAME>
|
||||
CustomLog /var/log/apache2/domlogs/<DOMAIN_NAME>.log combined
|
||||
|
||||
<Directory /home/<USER>/<DOMAIN_NAME>>
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
DirectoryIndex index.php index.html default_page.html
|
||||
|
||||
# Alias for default_page.html
|
||||
Alias /default_page.html /etc/apache2/default_page.html
|
||||
<Directory "/etc/apache2">
|
||||
<FilesMatch "^default_page.html$">
|
||||
Require all granted
|
||||
Options -Indexes
|
||||
SetEnvIf Request_URI ^/default_page.html allow_default_page
|
||||
</FilesMatch>
|
||||
</Directory>
|
||||
|
||||
<FilesMatch \.php$>
|
||||
SetHandler "proxy:unix:/run/php/<PHP>-fpm.sock|fcgi://localhost"
|
||||
</FilesMatch>
|
||||
|
||||
# Allow access to .well-known for Certbot
|
||||
<Directory "/home/<USER>/<DOMAIN_NAME>/.well-known">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
Alias /.well-known /home/<USER>/<DOMAIN_NAME>/.well-known
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName <DOMAIN_NAME>
|
||||
ServerAlias www.<DOMAIN_NAME>
|
||||
DocumentRoot /home/<USER>/<DOMAIN_NAME>
|
||||
CustomLog /var/log/apache2/domlogs/<DOMAIN_NAME>.log combined
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/apache2/ssl/cert.crt
|
||||
SSLCertificateKeyFile /etc/apache2/ssl/cert.key
|
||||
|
||||
# TLS configuration
|
||||
SSLProtocol TLSv1.2 TLSv1.3
|
||||
SSLCipherSuite HIGH:!aNULL:!MD5
|
||||
|
||||
# Proxy configuration for Varnish
|
||||
ProxyPass "/" "http://127.0.0.1:80/"
|
||||
ProxyPassReverse "/" "http://127.0.0.1:80/"
|
||||
ProxyPreserveHost On
|
||||
|
||||
<Directory /home/<USER>/<DOMAIN_NAME>>
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
DirectoryIndex index.php index.html default_page.html
|
||||
|
||||
# Alias for default_page.html
|
||||
Alias /default_page.html /etc/apache2/default_page.html
|
||||
<Directory "/etc/apache2">
|
||||
<FilesMatch "^default_page.html$">
|
||||
Require all granted
|
||||
Options -Indexes
|
||||
SetEnvIf Request_URI ^/default_page.html allow_default_page
|
||||
</FilesMatch>
|
||||
</Directory>
|
||||
|
||||
<FilesMatch \.php$>
|
||||
SetHandler "proxy:unix:/run/php/<PHP>-fpm.sock|fcgi://localhost"
|
||||
</FilesMatch>
|
||||
|
||||
</VirtualHost>
|
||||
1
configuration/openadmin/cluster/.gitkeep
Normal file
1
configuration/openadmin/cluster/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
[USERS]
|
||||
reseller=no
|
||||
impersonate=no
|
||||
|
||||
[RESELLERS]
|
||||
api_access=no
|
||||
|
||||
|
||||
[PANEL]
|
||||
login_ratelimit=5
|
||||
login_blocklimit=20
|
||||
|
||||
|
||||
[SECURITY]
|
||||
basic_auth=no
|
||||
basic_auth_username=
|
||||
basic_auth_password=
|
||||
|
||||
@@ -31,9 +31,10 @@ login_blocklimit=20
|
||||
session_duration=30
|
||||
session_lifetime=300
|
||||
password_reset=no
|
||||
weakpass=yes
|
||||
twofa_nag=yes
|
||||
how_to_guides=yes
|
||||
sidebar_color=dark
|
||||
color_mode=dark
|
||||
avatar_type=gravatar
|
||||
max_login_records=20
|
||||
activity_items_per_page=25
|
||||
@@ -66,9 +67,6 @@ api=off
|
||||
dev_mode=off
|
||||
template=
|
||||
admin_template=
|
||||
basic_auth=no
|
||||
basic_auth_username=
|
||||
basic_auth_password=
|
||||
screenshots=http://screenshots-api.openpanel.com/screenshot
|
||||
temporary_links=https://preview.openpanel.org/index.php
|
||||
|
||||
|
||||
73
configuration/services/sentinel/messages.yaml
Normal file
73
configuration/services/sentinel/messages.yaml
Normal file
@@ -0,0 +1,73 @@
|
||||
DAILY_REPORT:
|
||||
title: "Daily Usage Report"
|
||||
message: "Daily usage report."
|
||||
|
||||
REBOOT:
|
||||
title: "🚨 URGENT - SYSTEM REBOOT"
|
||||
message: "System was rebooted. Uptime: "
|
||||
|
||||
SWAP_USAGE:
|
||||
title: "⚠️ SWAP usage alert!"
|
||||
message: ""
|
||||
|
||||
RAM_USAGE:
|
||||
title: "🚨 High Memory Usage!"
|
||||
message: ""
|
||||
|
||||
LOAD_USAGE:
|
||||
title: "🚨 High System Load!"
|
||||
message: ""
|
||||
|
||||
CPU_USAGE:
|
||||
title: "🚨 High CPU Usage!"
|
||||
message: ""
|
||||
|
||||
DISK_USAGE:
|
||||
title: "⚠️ Running out of Disk Space!"
|
||||
message: ""
|
||||
|
||||
SSH_LOGIN:
|
||||
title: "Suspicious SSH login detected"
|
||||
message: "SSH users currently logged-in that have not accessed OpenAdmin interface. IP addresses: "
|
||||
|
||||
ADMIN_LOGIN:
|
||||
title: "Admin account accessed from new IP address"
|
||||
message: "Admin account was accessed from a new IP address: "
|
||||
|
||||
MYSQL_DOWN:
|
||||
title: "⚠️ WARNING - MySQL service is not active. Users are unable to log into OpenPanel!"
|
||||
message: ""
|
||||
|
||||
NGINX_DOWN:
|
||||
title: "Nginx service is not active. Users' websites are not working!"
|
||||
message: ""
|
||||
|
||||
CSF_DOWN:
|
||||
title: "ConfigService Firewall (CSF) is not active. Server and websites are not protected!"
|
||||
message: ""
|
||||
|
||||
UFW_DOWN:
|
||||
title: "Uncomplicated Firewall (UFW) is not active. Server and websites are not protected!"
|
||||
message: ""
|
||||
|
||||
ADMIN_DOWN:
|
||||
title: "Admin service is not active. OpenAdmin service is not accessible!"
|
||||
message: ""
|
||||
|
||||
DOCKER_DOWN:
|
||||
title: "Docker service is not active. User websites are down!"
|
||||
message: ""
|
||||
|
||||
PANEL_DOWN:
|
||||
title: "OpenPanel docker container is not running. Users are unable to access the OpenPanel interface!"
|
||||
message: ""
|
||||
|
||||
CERTBOT_DOWN:
|
||||
title: "Certbot service is not running. SSL certificates will not renew automatically!"
|
||||
message: ""
|
||||
|
||||
BIND_DOWN:
|
||||
title: "Named (BIND9) service is not active. DNS resolving of domains is not working!"
|
||||
message: ""
|
||||
|
||||
|
||||
31
configuration/services/varnish.service
Normal file
31
configuration/services/varnish.service
Normal file
@@ -0,0 +1,31 @@
|
||||
[Unit]
|
||||
Description=Varnish Cache, a high-performance HTTP accelerator
|
||||
Documentation=https://www.varnish-cache.org/docs/ man:varnishd
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
# Maximum number of open files (for ulimit -n)
|
||||
LimitNOFILE=131072
|
||||
|
||||
# Locked shared memory - should suffice to lock the shared memory log
|
||||
# (varnishd -l argument)
|
||||
# Default log size is 80MB vsl + 1M vsm + header -> 82MB
|
||||
# unit is bytes
|
||||
LimitMEMLOCK=85983232
|
||||
ExecStart=/usr/sbin/varnishd \
|
||||
-j unix,user=vcache \
|
||||
-F \
|
||||
-a :80 \
|
||||
-T localhost:6082 \
|
||||
-f /etc/varnish/default.vcl \
|
||||
-S /etc/varnish/secret \
|
||||
-s malloc,256m
|
||||
ExecReload=/usr/share/varnish/varnishreload
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -106,8 +106,8 @@ echo -e "OPENADMIN LINK: ${GREEN}${admin_url}${RESET}"
|
||||
echo -e ""
|
||||
echo -e "Need assistance or looking to learn more? We've got you covered:"
|
||||
echo -e " - 📚 Admin Docs: https://openpanel.com/docs/admin/intro/"
|
||||
echo -e " - 💬 Forums: https://community.openpanel.com/"
|
||||
echo -e " - 👉 Discord: https://discord.openpanel.com/"
|
||||
echo -e " - 💬 Forums: https://community.openpanel.org/"
|
||||
echo -e " - 👉 Discord: https://discord.openpanel.com/"
|
||||
echo -e ""
|
||||
echo -e "================================================================"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
vcl 4.1;
|
||||
|
||||
backend default {
|
||||
.host = "127.0.0.1";
|
||||
.host = "localhost";
|
||||
.port = "8080";
|
||||
}
|
||||
|
||||
|
||||
114
configuration/varnish/docker_varnish_nginx_domain.conf
Normal file
114
configuration/varnish/docker_varnish_nginx_domain.conf
Normal file
@@ -0,0 +1,114 @@
|
||||
# content
|
||||
server {
|
||||
listen 80;
|
||||
server_name <DOMAIN_NAME> www.<DOMAIN_NAME>;
|
||||
access_log /var/log/nginx/domlogs/<DOMAIN_NAME>.log;
|
||||
|
||||
# <!-- BEGIN EXPOSED RESOURCES PROTECTION -->
|
||||
location ~* ^/(\.git|composer\.(json|lock)|auth\.json|config\.php|wp-config\.php|vendor) {
|
||||
deny all;
|
||||
return 403;
|
||||
}
|
||||
# <!-- END EXPOSED RESOURCES PROTECTION -->
|
||||
|
||||
root /home/<USER>/<DOMAIN_NAME>;
|
||||
|
||||
location / {
|
||||
real_ip_header X-Forwarded-For;
|
||||
set_real_ip_from 172.17.0.1;
|
||||
try_files $uri $uri/ /index.php$is_args$args /default_page.html =404;
|
||||
index index.php index.html default_page.html;
|
||||
autoindex on;
|
||||
}
|
||||
|
||||
location = /default_page.html {
|
||||
alias /etc/nginx/default_page.html;
|
||||
default_type text/html;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:/run/php/<PHP>-fpm.sock;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
|
||||
expires max;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location = /favicon.ico {
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /robots.txt {
|
||||
allow all;
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name <DOMAIN_NAME> www.<DOMAIN_NAME>;
|
||||
access_log /var/log/nginx/domlogs/<DOMAIN_NAME>.log;
|
||||
|
||||
root /home/<USER>/<DOMAIN_NAME>;
|
||||
|
||||
# SSL configuration
|
||||
ssl_certificate /etc/nginx/ssl/cert.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/cert.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
|
||||
|
||||
# Proxy HTTPS traffic to Varnish on port 80
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:80;
|
||||
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 https;
|
||||
}
|
||||
|
||||
|
||||
location = /default_page.html {
|
||||
alias /etc/nginx/default_page.html;
|
||||
default_type text/html;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:/run/php/<PHP>-fpm.sock;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
|
||||
expires max;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location = /favicon.ico {
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /robots.txt {
|
||||
allow all;
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Allow access to .well-known for Certbot
|
||||
location ^~ /.well-known {
|
||||
default_type "text/plain";
|
||||
root /home/<USER>/<DOMAIN_NAME>/;
|
||||
}
|
||||
|
||||
}
|
||||
1
integrations/digitalocean/.github/CODEOWNERS
vendored
1
integrations/digitalocean/.github/CODEOWNERS
vendored
@@ -1 +0,0 @@
|
||||
@marketplace-eng
|
||||
@@ -1,27 +0,0 @@
|
||||
# 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
43
integrations/digitalocean/.gitignore
vendored
@@ -1,43 +0,0 @@
|
||||
# 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
|
||||
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)
|
||||
|
||||
805
integrations/whmcs/openpanel.php
Normal file
805
integrations/whmcs/openpanel.php
Normal file
@@ -0,0 +1,805 @@
|
||||
<?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) {
|
||||
// Only log token issues as these are critical.
|
||||
logModuleCall('openpanel', 'ChangePackage', $params, "Error fetching token: $error");
|
||||
return $error;
|
||||
}
|
||||
|
||||
try {
|
||||
$apiProtocol = getApiProtocol($params["serverhostname"]);
|
||||
$changePlanEndpoint = $apiProtocol . $params["serverhostname"] . ':2087/api/users/' . $params["username"];
|
||||
|
||||
// Fetch the stored plan ID
|
||||
$storedPlanId = $params['configoption1'];
|
||||
|
||||
// Retrieve available plans from the server
|
||||
$plans = getAvailablePlans($params);
|
||||
|
||||
if (is_string($plans)) {
|
||||
logModuleCall('openpanel', 'ChangePackage', $params, "Error retrieving plans: $plans");
|
||||
return "Error retrieving plans: $plans";
|
||||
}
|
||||
|
||||
// Find the actual plan name using the stored plan ID
|
||||
$planName = null;
|
||||
foreach ($plans as $plan) {
|
||||
if ($plan['id'] == $storedPlanId) {
|
||||
$planName = $plan['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$planName) {
|
||||
logModuleCall('openpanel', 'ChangePackage', $params, "No matching plan name found for stored plan ID: $storedPlanId");
|
||||
return "Error: No matching plan name found for stored plan ID: $storedPlanId";
|
||||
}
|
||||
|
||||
// Prepare data for changing plan
|
||||
$planData = array('plan_name' => $planName);
|
||||
|
||||
// Make API request to change plan
|
||||
$response = apiRequest($changePlanEndpoint, $jwtToken, $planData, 'PUT');
|
||||
|
||||
// Log only on failure
|
||||
if (!(isset($response['success']) && $response['success'] === true)) {
|
||||
logModuleCall('openpanel', 'ChangePackage', array('command' => "opencli user-change_plan {$params['username']} $planName"), $response);
|
||||
return isset($response['error']) ? $response['error'] : 'An unknown error occurred during package change.';
|
||||
}
|
||||
|
||||
return 'success';
|
||||
|
||||
} catch (Exception $e) {
|
||||
logModuleCall('openpanel', 'ChangePackage Exception', $params, $e->getMessage(), $e->getTraceAsString());
|
||||
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;
|
||||
}
|
||||
|
||||
function getAvailablePlans($params) {
|
||||
// Use WHMCS server parameters for OpenPanel
|
||||
$username = $params['serverusername']; // OpenPanel username
|
||||
$password = $params['serverpassword']; // OpenPanel password
|
||||
$hostname = $params['serverhostname']; // OpenPanel hostname
|
||||
|
||||
$apiProtocol = getApiProtocol($hostname);
|
||||
$plansEndpoint = $apiProtocol . $hostname . ':2087/api/plans'; // Correct endpoint for OpenPanel API
|
||||
|
||||
// Get the JWT token using the server credentials
|
||||
list($jwtToken, $error) = getAuthToken($params);
|
||||
if (!$jwtToken) {
|
||||
return "Error fetching token: $error"; // Return error if token cannot be fetched
|
||||
}
|
||||
|
||||
// Prepare cURL request with Bearer token
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => $plansEndpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
"Authorization: Bearer " . $jwtToken,
|
||||
"Content-Type: application/json"
|
||||
),
|
||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||
// Enable SSL verification for production
|
||||
CURLOPT_SSL_VERIFYHOST => 2, // Verify the SSL certificate's host
|
||||
CURLOPT_SSL_VERIFYPEER => true, // Verify the SSL certificate
|
||||
));
|
||||
|
||||
// Execute the request
|
||||
$response = curl_exec($curl);
|
||||
|
||||
// Capture any errors
|
||||
if (curl_errno($curl)) {
|
||||
return "cURL Error: " . curl_error($curl); // Return cURL error
|
||||
}
|
||||
|
||||
// Close cURL session
|
||||
curl_close($curl);
|
||||
|
||||
// Decode the response
|
||||
$plansResponse = json_decode($response, true);
|
||||
|
||||
// Check if the plans were retrieved
|
||||
if (isset($plansResponse['plans']) && is_array($plansResponse['plans'])) {
|
||||
return $plansResponse['plans']; // Return the plans array
|
||||
} else {
|
||||
return "Error fetching plans: " . json_encode($plansResponse); // Return error
|
||||
}
|
||||
}
|
||||
|
||||
function openpanel_ConfigOptions() {
|
||||
// Get the product ID from the request, if available
|
||||
$productId = isset($_REQUEST['id']) ? (int)$_REQUEST['id'] : 0;
|
||||
|
||||
if (!$productId) {
|
||||
// If no product ID exists yet, prompt the user to save first
|
||||
return array(
|
||||
'Note' => array(
|
||||
'Description' => 'Please save the product first to configure options.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch the server group assigned to this product
|
||||
$result = select_query('tblproducts', 'servergroup', array('id' => $productId));
|
||||
$data = mysql_fetch_array($result);
|
||||
$serverGroupId = $data['servergroup'];
|
||||
|
||||
if (!$serverGroupId) {
|
||||
// If no server group is selected yet, do not show the plans field
|
||||
return array(
|
||||
'Note' => array(
|
||||
'Description' => 'Please assign a server group to this product to fetch available plans.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch servers in the selected server group
|
||||
$serversResult = select_query('tblservers', '*', array('disabled' => 0));
|
||||
$servers = array();
|
||||
while ($serverData = mysql_fetch_array($serversResult)) {
|
||||
// Check if the server belongs to the selected server group
|
||||
$serverGroupRelResult = select_query('tblservergroupsrel', 'groupid', array('serverid' => $serverData['id']));
|
||||
while ($groupRel = mysql_fetch_array($serverGroupRelResult)) {
|
||||
if ($groupRel['groupid'] == $serverGroupId) {
|
||||
$servers[] = $serverData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($servers) == 0) {
|
||||
// No servers found in the group, show a message and don't load the plans field
|
||||
return array(
|
||||
'Note' => array(
|
||||
'Description' => 'No servers found in the assigned server group.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Use the first server in the group
|
||||
$server = $servers[0];
|
||||
$params = array(
|
||||
'serverhostname' => $server['hostname'],
|
||||
'serverusername' => $server['username'],
|
||||
'serverpassword' => decrypt($server['password']),
|
||||
);
|
||||
|
||||
// Fetch available plans from OpenPanel
|
||||
$plans = getAvailablePlans($params);
|
||||
|
||||
// Handle errors in fetching plans
|
||||
if (is_string($plans)) {
|
||||
// Error message
|
||||
return array(
|
||||
'Note' => array(
|
||||
'Description' => 'Error fetching plans: ' . $plans,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Populate plans in the dropdown
|
||||
$planOptions = array();
|
||||
if ($plans && is_array($plans)) {
|
||||
foreach ($plans as $plan) {
|
||||
$planOptions[$plan['id']] = $plan['name'];
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'Plan' => array(
|
||||
'Type' => 'dropdown',
|
||||
'Options' => $planOptions,
|
||||
'Description' => 'Select a plan from OpenPanel',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
############### 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\/"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user