mirror of
https://github.com/crivion/laranode.git
synced 2026-09-03 06:24:09 +08:00
Create & Delete Accounts - includes php-fpm pools now
This commit is contained in:
@@ -28,20 +28,11 @@ class AccountsController extends Controller
|
||||
*/
|
||||
public function store(CreateAccountRequest $request): RedirectResponse
|
||||
{
|
||||
try {
|
||||
(new CreateAccountService($request->validated()))->handle();
|
||||
|
||||
(new CreateAccountService($request->validated()))->handle();
|
||||
session()->flash('success', 'Account created successfully!');
|
||||
|
||||
session()->flash('success', 'Account created successfully!');
|
||||
|
||||
return redirect()->route('accounts.index');
|
||||
} catch (CreateAccountException $e) {
|
||||
session()->flash('error', $e->getMessage());
|
||||
return back();
|
||||
} catch (Exception $e) {
|
||||
session()->flash('error', $e->getMessage());
|
||||
return back();
|
||||
}
|
||||
return redirect()->route('accounts.index');
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +51,8 @@ class AccountsController extends Controller
|
||||
{
|
||||
(new DeleteAccountService(User::findOrFail($account)))->handle();
|
||||
|
||||
session()->flash('success', 'Account deleted successfully!');
|
||||
|
||||
return redirect()->route('accounts.index');
|
||||
}
|
||||
|
||||
|
||||
20
app/Models/PhpVersion.php
Normal file
20
app/Models/PhpVersion.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PhpVersion extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\PhpVersionFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'active' => 'boolean',
|
||||
'is_default' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Services\Accounts;
|
||||
|
||||
use App\Models\PhpVersion;
|
||||
use App\Models\User;
|
||||
use App\Services\Laranode\CreatePhpFpmPoolService;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Exception;
|
||||
@@ -17,7 +19,7 @@ class CreateAccountService
|
||||
public function __construct(private array $validated)
|
||||
{
|
||||
// path to laranode user manager bin|ssh script
|
||||
$this->laranodeBinPath = '/usr/local/bin/laranode';
|
||||
$this->laranodeBinPath = config('laranode.laranode_bin_path');
|
||||
|
||||
// appends _ln to all users to avoid all sort of issues (conflicts, control, security, files, etc.)
|
||||
$this->systemUsername = $validated['username'] . '_ln';
|
||||
@@ -55,8 +57,14 @@ class CreateAccountService
|
||||
if ($createUser->failed()) {
|
||||
throw new CreateAccountException('Failed to create system user: ' . $createUser->errorOutput());
|
||||
}
|
||||
|
||||
$this->createDefaultPHPFpmPool();
|
||||
}
|
||||
|
||||
// @TODO: implement add user php-fpm pools based on each php version
|
||||
private function addPhpFpmPools(): void {}
|
||||
private function createDefaultPHPFpmPool(): void
|
||||
{
|
||||
$defaultPhpVersion = PhpVersion::where('is_default', true)->firstOrFail();
|
||||
|
||||
(new CreatePhpFpmPoolService($this->systemUsername, $defaultPhpVersion))->handle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,22 @@ class DeleteAccountService
|
||||
|
||||
public function __construct(private User $user)
|
||||
{
|
||||
$this->laranodeBinPath = '/usr/local/bin/laranode';
|
||||
// path to laranode user manager bin|ssh script
|
||||
$this->laranodeBinPath = config('laranode.laranode_bin_path');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
// delete php-fpm pools
|
||||
$this->deletePhpFpmPools();
|
||||
|
||||
// wait for pools to be deleted && fpm to restart
|
||||
sleep(1);
|
||||
|
||||
// delete system user
|
||||
$this->deleteSystemUser();
|
||||
|
||||
// remove user from database
|
||||
User::findOrFail($this->user->id)->delete();
|
||||
}
|
||||
|
||||
@@ -39,9 +47,19 @@ class DeleteAccountService
|
||||
}
|
||||
}
|
||||
|
||||
private function deletePhpFpmPools(): void
|
||||
{
|
||||
$deletePhpFpmPool = Process::run([
|
||||
'sudo',
|
||||
$this->laranodeBinPath . '/laranode-remove-php-fpm-pool.sh',
|
||||
$this->user->systemUsername,
|
||||
]);
|
||||
|
||||
if ($deletePhpFpmPool->failed()) {
|
||||
throw new DeleteAccountException('Failed to delete PHP-FPM pool: ' . $deletePhpFpmPool->errorOutput());
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: implement delete all DB's of this user
|
||||
private function deleteDatabases(): void {}
|
||||
|
||||
// @TODO: implement delete user php-fpm pools
|
||||
private function deletePhpFpmPools(): void {}
|
||||
}
|
||||
|
||||
39
app/Services/Laranode/CreatePhpFpmPoolService.php
Normal file
39
app/Services/Laranode/CreatePhpFpmPoolService.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Laranode;
|
||||
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Exception;
|
||||
|
||||
class CreatePhpFpmPoolException extends Exception {}
|
||||
|
||||
class CreatePhpFpmPoolService
|
||||
{
|
||||
private string $laranodeBinPath;
|
||||
private string $phpFpmPoolTemplate;
|
||||
|
||||
public function __construct(private string $systemUser, private string $phpVersion)
|
||||
{
|
||||
// path to laranode user manager bin|ssh script
|
||||
$this->laranodeBinPath = config('laranode.laranode_bin_path');
|
||||
|
||||
// path to php-fpm pool template
|
||||
$this->phpFpmPoolTemplate = config('laranode.php_fpm_pool_template');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$createPhpFpmPool = Process::run([
|
||||
'sudo',
|
||||
$this->laranodeBinPath . '/laranode-add-php-fpm-pool.sh',
|
||||
$this->systemUser,
|
||||
'7.4', // @TODO: remove this and replace with $this->phpVersion
|
||||
$this->phpFpmPoolTemplate
|
||||
/*$this->phpVersion,*/
|
||||
]);
|
||||
|
||||
if ($createPhpFpmPool->failed()) {
|
||||
throw new CreatePhpFpmPoolException('Failed to create PHP-FPM pool: ' . $createPhpFpmPool->errorOutput());
|
||||
}
|
||||
}
|
||||
}
|
||||
37
config/laranode.php
Normal file
37
config/laranode.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Laranode User Manager
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to specify the path to the laranode user manager
|
||||
| binary. This is used to create and delete system users.
|
||||
|
|
||||
*/
|
||||
'laranode_bin_path' => base_path('laranode-scripts/bin'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Laranode PHP-FPM Pools
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to specify the path to the laranode PHP-FPM pool
|
||||
| configuration template. This is used to create and delete PHP-FPM pools.
|
||||
|
|
||||
*/
|
||||
'php_fpm_pool_template' => base_path('laranode-scripts/templates/php-fpm-pool.template'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Laranode Apache Virtual Hosts
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to specify the path to the laranode Apache virtual
|
||||
| host configuration template. This is used to create and delete Apache
|
||||
| virtual hosts.
|
||||
*/
|
||||
'apache_vhost_template' => base_path('laranode-scripts/templates/apache-vhost.template'),
|
||||
|
||||
];
|
||||
25
database/factories/PhpVersionFactory.php
Normal file
25
database/factories/PhpVersionFactory.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\PhpVersion>
|
||||
*/
|
||||
class PhpVersionFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'version' => '8.3',
|
||||
'active' => true,
|
||||
'is_default' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('php_versions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('version');
|
||||
$table->boolean('active')->default(false);
|
||||
$table->boolean('is_default')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('php_versions');
|
||||
}
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Database\Factories\PhpVersionFactory;
|
||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
@@ -13,11 +14,6 @@ class DatabaseSeeder extends Seeder
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// User::factory(10)->create();
|
||||
|
||||
User::factory()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
PhpVersionFactory::new()->create();
|
||||
}
|
||||
}
|
||||
|
||||
30
laranode-scripts/bin/laranode-add-php-fpm-pool.sh
Executable file
30
laranode-scripts/bin/laranode-add-php-fpm-pool.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check if at least two arguments are provided (system user and php version)
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Usage: $0 {system user} {php version} {template_file_path}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SYSTEM_USER=$1
|
||||
PHP_VERSION=$2
|
||||
TEMPLATE_FILE_PATH=$3
|
||||
|
||||
# Automatically append _ln to $USERNAME if not already present
|
||||
if echo "$SYSTEM_USER" | grep -qv '_ln$'; then
|
||||
SYSTEM_USER+="_ln"
|
||||
fi
|
||||
|
||||
# read template file
|
||||
TEMPLATE_FILE=$(cat "$TEMPLATE_FILE_PATH")
|
||||
|
||||
# replace {user} and {version} in template file
|
||||
TEMPLATE_FILE=$(echo "$TEMPLATE_FILE" | sed "s/{user}/$SYSTEM_USER/g")
|
||||
TEMPLATE_FILE=$(echo "$TEMPLATE_FILE" | sed "s/{version}/$PHP_VERSION/g")
|
||||
|
||||
# write template file to /etc/php/{version}/fpm/pool.d/pool-{systemUser}.conf
|
||||
echo "$TEMPLATE_FILE" > "/etc/php/$PHP_VERSION/fpm/pool.d/$SYSTEM_USER.conf"
|
||||
|
||||
# reload php{version}-fpm
|
||||
echo "Reloading php$PHP_VERSION-fpm..."
|
||||
systemctl reload php"$PHP_VERSION"-fpm
|
||||
33
laranode-scripts/bin/laranode-remove-php-fpm-pool.sh
Executable file
33
laranode-scripts/bin/laranode-remove-php-fpm-pool.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 SYSTEM_USER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SYSTEM_USER="$1"
|
||||
count=0
|
||||
affected_versions=()
|
||||
|
||||
# Find and process pool configs
|
||||
while IFS= read -r file; do
|
||||
if [ -f "$file" ]; then
|
||||
version=$(echo "$file" | grep -oP '/etc/php/\K[0-9]+\.[0-9]+')
|
||||
echo "Removing $file"
|
||||
rm -v "$file"
|
||||
affected_versions+=("$version")
|
||||
((count++))
|
||||
fi
|
||||
done < <(find /etc/php/*/fpm/pool.d -name "${SYSTEM_USER}.conf")
|
||||
|
||||
echo "Removed $count pool configuration(s)"
|
||||
|
||||
# Restart only affected PHP-FPM versions
|
||||
for version in "${affected_versions[@]}"; do
|
||||
echo "Restarting php${version}-fpm"
|
||||
systemctl restart "php${version}-fpm"
|
||||
done
|
||||
|
||||
if [ $count -eq 0 ]; then
|
||||
echo "No pool configurations found for $SYSTEM_USER"
|
||||
fi
|
||||
99
laranode-scripts/bin/laranode-user-manager.sh
Executable file
99
laranode-scripts/bin/laranode-user-manager.sh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check if at least two arguments are provided (action and username)
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 {create|delete} username {allow shell/ssh login (optional): yes|no} {password (required if allow shell/ssh login is yes)}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACTION=$1
|
||||
USERNAME=$2
|
||||
ALLOW_LOGIN=${3:-no} # Default to "no" if not specified
|
||||
PASSWORD=$4
|
||||
|
||||
# Ensure password is provided if allow shell/ssh login is "yes"
|
||||
if [ "$ALLOW_LOGIN" = "yes" ] && [ -z "$PASSWORD" ]; then
|
||||
echo "Error: Password is required when allow shell/ssh login is 'yes'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Automatically append _ln to $USERNAME if not already present
|
||||
if echo "$USERNAME" | grep -qv '_ln$'; then
|
||||
USERNAME+="_ln"
|
||||
fi
|
||||
|
||||
# Function to create a user
|
||||
create_user() {
|
||||
# Check if the user already exists
|
||||
if id "$USERNAME" &>/dev/null; then
|
||||
echo "User $USERNAME already exists."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create the user
|
||||
if [ "$ALLOW_LOGIN" = "yes" ]; then
|
||||
# Create user with login shell
|
||||
useradd -m "$USERNAME"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to create user $USERNAME."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# add password to user
|
||||
echo "$USERNAME:$PASSWORD" | chpasswd
|
||||
echo "User $USERNAME created successfully with SSH login allowed."
|
||||
else
|
||||
# Create user with no login shell
|
||||
useradd -m -s /usr/sbin/nologin "$USERNAME"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to create user $USERNAME."
|
||||
exit 1
|
||||
fi
|
||||
echo "User $USERNAME created successfully with no SSH login."
|
||||
fi
|
||||
|
||||
# add this user group to www-data group too
|
||||
usermod -aG "$USERNAME" www-data
|
||||
|
||||
# create /home/{user}/logs directory
|
||||
mkdir -p "/home/$USERNAME/logs"
|
||||
chown "$USERNAME:$USERNAME" "/home/$USERNAME/logs"
|
||||
chmod 770 "/home/$USERNAME/logs"
|
||||
}
|
||||
|
||||
# Function to delete a user
|
||||
delete_user() {
|
||||
# Check if the user exists
|
||||
if ! id "$USERNAME" &>/dev/null; then
|
||||
echo "User $USERNAME does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# remove this user from www-data's groups
|
||||
deluser www-data "$USERNAME"
|
||||
|
||||
# Delete the user and their home directory
|
||||
userdel -r "$USERNAME"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to delete user $USERNAME."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
echo "User $USERNAME deleted successfully."
|
||||
}
|
||||
|
||||
# Perform the action based on the first argument (create or delete)
|
||||
case $ACTION in
|
||||
create)
|
||||
create_user
|
||||
;;
|
||||
delete)
|
||||
delete_user
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid action. Use 'create' or 'delete'."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
8
laranode-scripts/templates/apache-vhost.template
Normal file
8
laranode-scripts/templates/apache-vhost.template
Normal file
@@ -0,0 +1,8 @@
|
||||
# @TODO: complete this
|
||||
<VirtualHost *:80>
|
||||
ServerName {domain}
|
||||
DocumentRoot /home/{user}/public
|
||||
|
||||
ErrorLog /home/{user}/logs/apache-error.log
|
||||
CustomLog /home/{user}/logs/apache-access.log combined
|
||||
</VirtualHost>
|
||||
33
laranode-scripts/templates/php-fpm-pool.template
Normal file
33
laranode-scripts/templates/php-fpm-pool.template
Normal file
@@ -0,0 +1,33 @@
|
||||
[{user}]
|
||||
|
||||
; configure user & group
|
||||
user = {user}
|
||||
group = {user}
|
||||
|
||||
; configure socket
|
||||
listen = /run/php/php-fpm-{version}.{user}.sock
|
||||
listen.owner = {user}
|
||||
listen.group = {user}
|
||||
listen.mode = 0660
|
||||
|
||||
; pool settings
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
pm.max_requests = 500
|
||||
|
||||
; Security settings
|
||||
php_admin_value[open_basedir] = /home/{user}:/tmp/
|
||||
php_admin_flag[expose_php] = Off
|
||||
|
||||
; Performance settings
|
||||
php_admin_value[memory_limit] = 128M
|
||||
php_admin_value[upload_max_filesize] = 128M
|
||||
php_admin_value[post_max_size] = 128M
|
||||
|
||||
; Error handling
|
||||
php_admin_value[error_log] = /home/{user}/logs/php-fpm-error.log
|
||||
php_admin_flag[log_errors] = on
|
||||
php_admin_flag[display_errors] = on
|
||||
@@ -88,17 +88,19 @@ const SidebarNavi = () => {
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
to="/admin/php-manager"
|
||||
className="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-900 text-gray-300 border-l-4 border-transparent hover:border-indigo-900 pr-6"
|
||||
>
|
||||
<div>
|
||||
<FaPhp className="ml-3 w-5 h-5" />
|
||||
</div>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">PHP Manager</span>
|
||||
</Link>
|
||||
</li>
|
||||
{auth.user.role == 'admin' && (
|
||||
<li>
|
||||
<Link
|
||||
to="/php-manager"
|
||||
className="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-900 text-gray-300 border-l-4 border-transparent hover:border-indigo-900 pr-6"
|
||||
>
|
||||
<div>
|
||||
<FaPhp className="ml-3 w-5 h-5" />
|
||||
</div>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">PHP Manager</span>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
<Link
|
||||
|
||||
@@ -12,10 +12,11 @@ import { FaDatabase, FaEdit } from "react-icons/fa";
|
||||
|
||||
export default function Accounts({ accounts }) {
|
||||
|
||||
|
||||
const deleteUser = (id) => {
|
||||
router.delete(route('accounts.destroy', { account: id }), {
|
||||
onSuccess: page => {
|
||||
toast("Account deleted successfully.");
|
||||
onBefore: () => {
|
||||
toast("Please wait, deleting account and it's resources...");
|
||||
},
|
||||
onError: errors => {
|
||||
toast("Error occured while deleting account.");
|
||||
|
||||
@@ -60,7 +60,6 @@ export default function CreateAccountForm() {
|
||||
onSuccess: () => {
|
||||
closeModal();
|
||||
reset();
|
||||
toast("Account created successfully.");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user