Auto-commit on 2024-12-03 10:35:31 by pcx3

This commit is contained in:
Stefan
2024-12-03 10:35:31 +01:00
parent 764d7e876f
commit 75bb525d70
24 changed files with 1212 additions and 196 deletions

View File

@@ -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

View File

@@ -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"}'

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -1,3 +0,0 @@
#.github/
#.github/workflows/
README.md

View File

@@ -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

View File

@@ -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

View 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

View 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>

View File

@@ -0,0 +1 @@

View File

@@ -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=

View File

@@ -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

View 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: ""

View 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

View File

@@ -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 "================================================================"

View File

@@ -1,7 +1,7 @@
vcl 4.1;
backend default {
.host = "127.0.0.1";
.host = "localhost";
.port = "8080";
}

View 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>/;
}
}

View File

@@ -1 +0,0 @@
@marketplace-eng

View File

@@ -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.

View File

@@ -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

View 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)

View 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> &nbsp; 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> &nbsp; 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;
}
?>

View 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\/"
}
]
}