dhcp-symfony #1

Merged
lgromero merged 18 commits from dhcp-symfony into main 2024-09-30 12:13:29 +02:00
63 changed files with 3600 additions and 0 deletions

40
.env 100644
View File

@ -0,0 +1,40 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=d423d1302b974417d415b10bcde25767
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8&charset=utf8mb4"
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8"
###< doctrine/doctrine-bundle ###
###> symfony/messenger ###
# Choose one of the transports below
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
###< symfony/messenger ###
###> symfony/mailer ###
# MAILER_DSN=null://null
###< symfony/mailer ###

33
.gitignore vendored 100644
View File

@ -0,0 +1,33 @@
/app/bootstrap.php.cache
/app/cache/*
/app/config/parameters.yml
/app/logs/*
!app/cache/.gitkeep
!app/logs/.gitkeep
/app/phpunit.xml
/bin/
/build/
/composer.phar
/var/*
/vendor/
/web/bundles/
/web/uploads/
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###
###> phpunit/phpunit ###
/phpunit.xml
.phpunit.result.cache
###< phpunit/phpunit ###
###> symfony/phpunit-bridge ###
.phpunit.result.cache
/phpunit.xml
###< symfony/phpunit-bridge ###

107
composer.json 100644
View File

@ -0,0 +1,107 @@
{
"type": "project",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=7.2.0",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/annotations": "^1.6",
"doctrine/doctrine-bundle": "^2.0",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.7",
"phpdocumentor/reflection-docblock": "^5.0",
"phpstan/phpdoc-parser": "^0.4",
"zircote/swagger-php": "3.*",
"symfony/runtime": "5.*",
"symfony/asset": "5.*",
"symfony/console": "5.*",
"symfony/doctrine-messenger": "5.*",
"symfony/dotenv": "5.*",
"symfony/expression-language": "5.*",
"symfony/flex": "^1.17",
"symfony/form": "5.*",
"symfony/framework-bundle": "5.*",
"symfony/http-client": "5.*",
"symfony/intl": "5.*",
"symfony/mailer": "5.*",
"symfony/mime": "5.*",
"symfony/monolog-bundle": "^3.0",
"symfony/notifier": "5.*",
"symfony/process": "5.*",
"symfony/property-access": "5.*",
"symfony/property-info": "5.*",
"symfony/security-bundle": "5.*",
"symfony/serializer": "5.*",
"symfony/string": "5.*",
"symfony/translation": "5.*",
"symfony/twig-bundle": "5.*",
"symfony/validator": "5.*",
"symfony/web-link": "5.*",
"symfony/yaml": "5.*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"symfony/browser-kit": "5.*",
"symfony/css-selector": "5.*",
"symfony/debug-bundle": "5.*",
"symfony/maker-bundle": "^1.0",
"symfony/phpunit-bridge": "^5.0",
"symfony/stopwatch": "5.*",
"symfony/web-profiler-bundle": "5.*"
},
"config": {
"platform": {
"php": "8.2"
},
"allow-plugins": {
"composer/package-versions-deprecated": true,
"symfony/flex": true,
"symfony/runtime": true
},
"optimize-autoloader": true,
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.*"
}
}
}

15
config/bundles.php 100644
View File

@ -0,0 +1,15 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
App\DhcpBundle\DhcpBundle::class => ['all' => true],
];

View File

@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null

View File

@ -0,0 +1,5 @@
when@dev:
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"

View File

@ -0,0 +1,43 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '15'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
auto_generate_proxy_classes: false
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false

View File

@ -0,0 +1,24 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
http_method_override: false
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
storage_factory_id: session.storage.factory.native
#esi: true
#fragments: true
php_errors:
log: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

View File

@ -0,0 +1,3 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'

View File

@ -0,0 +1,24 @@
framework:
messenger:
failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
use_notify: true
check_delayed_interval: 60000
retry_strategy:
max_retries: 3
multiplier: 2
failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
Symfony\Component\Notifier\Message\ChatMessage: async
Symfony\Component\Notifier\Message\SmsMessage: async
# Route your messages to the transports
# 'App\Message\YourMessage': async

View File

@ -0,0 +1,61 @@
monolog:
channels:
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
when@dev:
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:
# type: firephp
# level: info
#chromephp:
# type: chromephp
# level: info
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
when@test:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
channels: ["!event"]
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
when@prod:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
nested:
type: stream
path: php://stderr
level: debug
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
channels: [deprecation]
path: php://stderr

View File

@ -0,0 +1,12 @@
framework:
notifier:
chatter_transports:
texter_transports:
channel_policy:
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
urgent: ['email']
high: ['email']
medium: ['email']
low: ['email']
admin_recipients:
- { email: admin@example.com }

View File

@ -0,0 +1,12 @@
framework:
router:
utf8: true
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
#default_uri: http://localhost
when@prod:
framework:
router:
strict_requirements: null

View File

@ -0,0 +1,40 @@
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_in_memory
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View File

@ -0,0 +1,13 @@
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- en
# providers:
# crowdin:
# dsn: '%env(CROWDIN_DSN)%'
# loco:
# dsn: '%env(LOCO_DSN)%'
# lokalise:
# dsn: '%env(LOKALISE_DSN)%'

View File

@ -0,0 +1,6 @@
twig:
default_path: '%kernel.project_dir%/templates'
when@test:
twig:
strict_variables: true

View File

@ -0,0 +1,13 @@
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false

View File

@ -0,0 +1,15 @@
when@dev:
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler: { only_exceptions: false }
when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View File

@ -0,0 +1,5 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

View File

@ -0,0 +1,3 @@
#index:
# path: /
# controller: App\Controller\DefaultController::index

View File

@ -0,0 +1,7 @@
controllers:
resource: ../../src/DhcpBundle/Controller/
type: annotation
kernel:
resource: ../../src/Kernel.php
type: annotation

View File

@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

View File

@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View File

@ -0,0 +1,27 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\DhcpBundle\Controller\:
resource: '../src/DhcpBundle/Controller'
tags: ['controller.service_arguments']

View File

@ -0,0 +1,121 @@
## API de DHCP
La API de DHCP proporciona una interfaz para interactuar con el servidor DHCP de Kea. Esta API permite realizar operaciones como obtener la configuración actual del servidor DHCP, actualizar la configuración y obtener información sobre las reservas de direcciones IP.
Los endpoints están agrupados en el recurso al que hace referencia. Actualmente se gestionan dos recursos:
- `/dhcp/subnets/`: CRUD de subredes de Kea DHCP. Las subredes serán donde se organizarán los hosts y hacen referencia a las subredes montadas por el aula.
- `/dhcp/subnets/hosts/`: CRUD de hosts en la configuración de Kea DHCP. Los hosts hacen referencia a los ordenadores dados de alta en las aulas. Esta sección en Kea DHCP se encarga de asignar, dada la MAC del dispositivo, la IP y el boot-file-name del ordenador.
El presente documento detalla los endpoints del API con sus respectivos parámetros de entrada así como los cambios que aplican sobre la configuración de Kea DHCP.
### Recurso `/dhcp/subnets`
#### Obtener configuración de las subredes
Devuelve las subredes dadas de alta en la configuración de Kea DHCP.
**Método HTTP:** GET
**URL:** `/dhcp/subnets`
#### Añadir subred
Añade un nueva subred a la configuración de Kea DHCP.
**Método HTTP:** POST
**URL:** `/dhcp/subnets`
**Parámetros de entrada:**
- `name`: Nombre de la subred.
- `subnet`: DirecciónIP de la subred.
- `boot-file-name`: Archivo de arranque de la subred.
- `nextServer`: Dirección IP del next-server.
#### Borrar subred DHCP
Borrar una subred de la configuración de Kea DHCP.
**Método HTTP:** DELETE
**URL:** `/dhcp/subnets/{id_subnet}`
#### Modificar Host DHCP
Modificar la subred en la configuración de Kea DHCP.
**Método HTTP:** PUT
**URL:** `/dhcp/subnets/{id_subnet}`
**Parámetros de entrada:**
- `name`: Nombre de la subred.
- `subnet`: DirecciónIP de la subred.
- `boot-file-name`: Archivo de arranque de la subred.
- `nextServer`: Dirección IP del next-server.
### Recurso `/dhcp/subnets/hosts`
#### Obtener configuración de los hosts
Devuelve la configuración de los hosts que se encuentran bajo una subred en la configuración de Kea DHCP.
**Método HTTP:** GET
**URL:** `/dhcp/subnets/{id_subnet}/hosts`
#### Añadir Host DHCP
Añade un nuevo host a la subnet especificada.
**Método HTTP:** POST
**URL:** `/dhcp/subnets/{id_subnet}/hosts`
**Parámetros de entrada:**
- `host`: Nombre del host.
- `macAddress`: Dirección MAC del host.
- `address`: Dirección IP del host.
- `nextServer`: Dirección IP del next-server.
#### Borrar Host DHCP
Borrar un host de la subnet especificada.
**Método HTTP:** DELETE
**URL:** `/dhcp/subnets/{id_subnet}/hosts`
**Parámetros de entrada:**
- `host`: Nombre del host.
#### Modificar Host DHCP
Modificar la configuración de un host en la subnet especificada.
**Método HTTP:** PUT
**URL:** `/dhcp/subnets/{id_subnet}/hosts`
**Parámetros de entrada:**
- `host`: Nombre del host.
- `oldMacAddress`: Dirección MAC antigua del host.
- `oldAddress`: Dirección IP antigua del host.
- `macAddress`: Nueva dirección MAC del host.
- `address`: Nueva dirección IP del host.
- `nextServer`: Nueva dirección IP del servidor siguiente.

View File

@ -0,0 +1,29 @@
# OpenGnsys ogdhcp configuration for Apache.
<VirtualHost *:80>
ServerName localhost
DocumentRoot /opt/ogdhcp/public
<Directory /opt/ogdhcp/public>
AllowOverride None
Require all granted
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET,POST,OPTIONS,DELETE,PUT"
Header set Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token"
FallbackResource /index.php
</Directory>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/opengnsys3/rest/dhcp/index\.php - [L]
RewriteRule ^/opengnsys3/rest/dhcp/(.*)$ /opengnsys3/rest/dhcp/index.php [QSA,L]
</IfModule>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost/"
</FilesMatch>
ErrorLog ${APACHE_LOG_DIR}/ogdhcp_error.log
CustomLog ${APACHE_LOG_DIR}/ogdhcp_access.log combined
</VirtualHost>

View File

@ -0,0 +1,571 @@
#!/usr/bin/env bash
#
# The core commands execute start from the "MAIN" section below.
#
test -z "$BASH_SOURCE" && {
self="sudo -E bash"
prefix="<curl command> |"
} || {
self=$(readlink -f ${BASH_SOURCE:-$0})
prefix=""
}
tmp_log=$(mktemp .csm_setup_XXXXXXXXX)
colours=$(tput colors 2>/dev/null || echo "256")
no_colour="\e[39;49m"
green_colour="\e[32m"
red_colour="\e[41;97m"
bold="\e[1m"
reset="\e[0m"
use_colours=$(test -n "$colours" && test $colours -ge 8 && echo "yes")
test "$use_colours" == "yes" || {
no_colour=""
green_colour=""
red_colour=""
bold=""
reset=""
}
example_name="Ubuntu/Xenial (16.04)"
example_distro="ubuntu"
example_codename="xenial"
example_version="16.04"
function echo_helptext {
local help_text="$*"
echo " ^^^^: ... $help_text"
}
function die {
local text="$@"
test ! -z "$text" && {
echo_helptext "$text" 1>&2
}
local prefix="${red_colour} !!!!${no_colour}"
echo -e "$prefix: Oh no, your setup failed! :-( ... But we might be able to help. :-)"
echo -e "$prefix: "
echo -e "$prefix: ${bold}You can contact ISC - Internet Systems Consortium for further assistance.${reset}"
echo -e "$prefix: "
echo -e "$prefix: ${bold}URL: https://www.isc.org${reset}"
echo -e "$prefix: "
test -f "$tmp_log" && {
local n=20
echo -e "$prefix: Last $n log lines from $tmp_log (might not be errors, nor even relevant):"
echo -e "$prefix:"
check_tool_silent "xargs" && {
check_tool_silent "fmt" && {
tail -n $n $tmp_log | fmt -t | xargs -Ilog echo -e "$prefix: > log"
} || {
tail -n $n $tmp_log | xargs -Ilog echo -e "$prefix: > log"
}
} || {
echo
tail -n $n $tmp_log
}
}
exit 1
}
function echo_colour {
local colour="${1:-"no"}_colour"; shift
echo -e "${!colour}$@${no_colour}"
}
function echo_green_or_red {
local rc="$1"
local good="${2:-YES}"
local bad="${3:-NO}"
test "$rc" -eq 0 && {
echo_colour "green" "$good"
} || {
echo_colour "red" "$bad"
}
return $rc
}
function echo_clearline {
local rc="$?"
echo -e -n "\033[1K\r"
return $rc
}
function echo_status {
local rc="$1"
local good="$2"
local bad="$3"
local text="$4"
local help_text="$5"
local newline=$(test "$6" != "no" && echo "\n" || echo "")
local status_text=$(echo_green_or_red "$rc" "$good" "$bad")
echo_clearline
local width=$(test "$use_colours" == "yes" && echo "16" || echo "5")
printf "%${width}s %s${newline}" "${status_text}:" "$text"
test $rc -ne 0 && test ! -z "$help_text" && {
echo_helptext "$help_text"
echo
}
return $rc
}
function echo_running {
local rc=$?
local text="$1"
echo_status 0 " RUN" " RUN" "$text" "" "no"
return $rc
}
function echo_okfail_rc {
local rc=$1
local text="$2"
local help_text="$3"
echo_clearline
echo_status $rc " OK" " NOPE" "$text" "$help_text"
return $rc
}
function echo_okfail {
echo_okfail_rc $? "$@"
return $?
}
function check_tool_silent {
local tool=${1}
command -v $tool &>/dev/null || which $tool &>/dev/null
return $?
}
function check_tool {
local tool=${1}
local optional=${2:-false}
local required_text="optional"
if ! $optional; then required_text="required"; fi
local text="Checking for $required_text executable '$tool' ..."
echo_running "$text"
check_tool_silent "$tool"
echo_okfail "$text" || {
if ! $optional; then
die "$tool is not installed, but is required by this script."
fi
return 1
}
return 0
}
function cleanup {
echo
rm -rf $tmp_log
}
function shutdown {
echo_colour "red" " !!!!: Operation cancelled by user!"
exit 2
}
function check_os {
test ! -z "$distro" && test ! -z "${version}${codename}"
return $?
}
function detect_os_system {
check_os && return 0
echo_running "$text"
local text="Detecting your OS distribution and release using system methods ..."
local tool_rc=1
test -f '/etc/os-release' && {
. /etc/os-release
distro=${distro:-$ID}
codename=${codename:-$VERSION_CODENAME}
codename=${codename:-$(echo $VERSION | cut -d '(' -f 2 | cut -d ')' -f 1)}
version=${version:-$VERSION_ID}
test -z "${version}${codename}" && test -f '/etc/debian_version' && {
# Workaround for Debian unstable releases; get the codename from debian_version
codename=$(cat /etc/debian_version | cut -d '/' -f1)
}
tool_rc=0
}
check_os
local rc=$?
echo_okfail_rc $rc "$text"
test $tool_rc -eq 0 && {
report_os_expanded
}
return $rc
}
function report_os_attribute {
local name=$1
local value=$2
local coloured=""
echo -n "$name="
test -z "$value" && {
echo -e -n "${red_colour}<empty>${no_colour} "
} || {
echo -e -n "${green_colour}${value}${no_colour} "
}
}
function report_os_expanded {
echo_helptext "Detected/provided for your OS/distribution, version and architecture:"
echo " >>>>:"
report_os_values
}
function report_os_values {
echo -n " >>>>: ... "
report_os_attribute "distro" $distro
report_os_attribute "version" $version
report_os_attribute "codename" $codename
report_os_attribute "arch" $arch
echo
echo " >>>>:"
}
function detect_os_legacy_python {
check_os && return 0
local text="Detecting your OS distribution and release using legacy python ..."
echo_running "$text"
IFS='' read -r -d '' script <<-'EOF'
from __future__ import unicode_literals, print_function
import platform;
info = platform.linux_distribution() or ('', '', '');
for key, value in zip(('distro', 'version', 'codename'), info):
print("local guess_%s=\"%s\"\n" % (key, value.lower().replace(' ', '')));
EOF
local tool_rc=1
check_tool_silent "python" && {
eval $(python -c "$script")
distro=${distro:-$guess_distro}
codename=${codename:-$guess_codename}
version=${version:-$guess_version}
tool_rc=$?
}
check_os
local rc=$?
echo_okfail_rc $rc "$text"
check_tool_silent "python" || {
echo_helptext "Python isn't available, so skipping detection method (hint: install python)"
}
test $tool_rc -eq 0 && {
report_os
}
return $rc
}
function detect_os_modern_python {
check_os && return 0
check_tool_silent "python" && {
local text="Ensuring python-pip is installed ..."
echo_running "$text"
check_tool_silent "pip"
echo_okfail "$text" || {
local text="Checking if pip can be bootstrapped without get-pip ..."
echo_running "$text"
python -m ensurepip --default-pip &>$tmp_log
echo_okfail "$text" || {
local text="Installing pip via get-pip bootstrap ..."
echo_running "$text"
curl -1sLf https://bootstrap.pypa.io/get-pip.py 2>$tmp/log | python &>$tmp_log
echo_okfail "$text" || die "Failed to install pip!"
}
}
# FIXME(ls): Install distro into a temporary virtualenv
local text="Installing 'distro' python library ..."
echo_running "$text"
python -c 'import distro' &>$tmp_log || python -m pip install distro &>$tmp_log
echo_okfail "$text" || die "Failed to install required 'distro' python library!"
}
IFS='' read -r -d '' script <<-'EOF'
from __future__ import unicode_literals, print_function
import distro;
info = distro.linux_distribution(full_distribution_name=False) or ('', '', '');
for key, value in zip(('distro', 'version', 'codename'), info):
print("local guess_%s=\"%s\"\n" % (key, value.lower().replace(' ', '')));
EOF
local text="Detecting your OS distribution and release using modern python ..."
echo_running "$text"
local tool_rc=1
check_tool_silent "python" && {
eval $(python -c "$script")
distro=${distro:-$guess_distro}
codename=${codename:-$guess_codename}
version=${version:-$guess_version}
tool_rc=$?
}
check_os
local rc=$?
echo_okfail_rc $rc "$text"
check_tool_silent "python" || {
echo_helptext "Python isn't available, so skipping detection method (hint: install python)"
}
test $tool_rc -eq 0 && {
report_os_expanded
}
return $rc
}
function detect_os {
# Backwards compat for old distribution parameter names
distro=${distro:-$os}
codename=${codename:-$dist}
arch=${arch:-$(arch || uname -m)}
detect_os_system ||
detect_os_legacy_python ||
detect_os_modern_python
(test -z "$distro" || test -z "${version}${codename}") && {
echo_okfail_rc "1" "Unable to detect your OS distribution and/or release!"
cat <<EOF
>>>>:
>>>>: The 'distro' value is required, and either 'version' or 'codename' values,
>>>>: or both. Without these, the install script cannot retrieve the correct
>>>>: configuration for this system.
>>>>:
>>>>: You can force this script to use a particular value by specifying distro,
>>>>: version, or codename via environment variable. E.g., to specify a distro
>>>>: such as $example_name, use the following:
>>>>:
>>>>: $prefix distro=$example_distro version=$example_version codename=$example_codename $self
>>>>:
EOF
die
}
}
function config_url {
echo "https://dl.cloudsmith.io/public/isc/kea-2-0/config.deb.txt?distro=${distro}&codename=${codename}&version=${version}&arch=${arch}" | sed 's/ /%20/g'
}
function check_fetch_config {
local text="Checking if upstream install config is OK ..."
echo_running "$text"
local code="$(curl -1IsL -w "%{http_code}\\n" "$(config_url)" -o /dev/null --connect-timeout 15 --max-time 60)"
test "$code" == "200" && {
echo_okfail_rc 0 "$text"
return 0
} || {
echo_okfail_rc 1 "$text"
echo_helptext "Failed to fetch configuration for your OS distribution release/version."
cat <<EOF
>>>>:
EOF
test "$code" == "404" && {
cat <<EOF
>>>>: It looks like we don't currently support your distribution release and
>>>>: version. This is something that we can fix by adding it to our list of
>>>>: supported versions (see contact us below), or you can manually override
>>>>: the values below to an equivalent distribution that we do support:
>>>>:
EOF
report_os_values
} || {
cat <<EOF
>>>>: It looks like it might be because the configuration endpoint is not
>>>>: currently available. Please try again in 10-15 minutes. If it still
>>>>: doesn't work after an hour, please contact ISC - Internet Systems Consortium
>>>>: for assistance.
>>>>:
EOF
}
cat <<EOF
>>>>: You can force this script to use a particular value by specifying distro,
>>>>: version, or codename via environment variable. E.g., to specify a distro
>>>>: such as $example_name, use the following:
>>>>:
>>>>: $prefix distro=$example_distro version=$example_version codename=$example_codename $self
>>>>:
EOF
die
}
}
function fetch_config {
curl -1sLf "$(config_url)"
}
function check_dpkg_tool {
local tool=${1}
local required=${2:-true}
local install=${3:-true}
local text="Checking for apt dependency '$tool' ..."
echo_running "$text"
dpkg -l | grep "$tool\>" &>$tmp_log
echo_okfail "$text" || {
if $install; then
test "$apt_updated" == "yes" || update_apt
local text="Attempting to install '$tool' ..."
echo_running "$text"
apt-get install -y "$tool" &>$tmp_log
echo_okfail "$text" || {
if $required; then
die "Could not install '$tool', check your permissions, etc."
fi
}
else
if $required; then
die "$tool is not installed, but is required by this script."
fi
fi
}
return 0
}
function update_apt {
local text="Updating apt repository metadata cache ..."
local tmp_log=$(mktemp .csm_deb_output_XXXXXXXXX.log)
echo_running "$text"
apt-get update &>$tmp_log
echo_okfail "$text" || {
echo_colour "red" "Failed to update via apt-get update"
cat $tmp_log
rm -rf $tmp_log
die "Failed to update via apt-get update - Context above."
}
rm -rf $tmp_log
apt_updated="yes"
}
function install_apt_prereqs {
# Debian-archive-keyring has to be installed for apt-transport-https.
test "${os}" == "debian" && {
check_dpkg_tool "debian-keyring"
check_dpkg_tool "debian-archive-keyring"
}
check_dpkg_tool "apt-transport-https"
check_dpkg_tool "ca-certificates" false
check_dpkg_tool "gnupg"
}
function import_gpg_key {
local text="Importing 'isc/kea-2-0' repository GPG keys ..."
echo_running "$text"
local gpg_keyring_path="/usr/share/keyrings/isc-kea-2-0-archive-keyring.gpg"
curl -1sLf "https://dl.cloudsmith.io/public/isc/kea-2-0/gpg.8029D4AFA58CBB5E.key" | gpg --dearmor >> $gpg_keyring_path
local signed_by_version="1.1"
local detected_version=$(dpkg -s apt | grep Version | cut -d' ' -f2)
[ "$(printf "%s\n" $detected_version $signed_by_version | sort -V | head -n 1)" == "$signed_by_version" ]
echo_okfail "Checking for apt signed-by key support ..." || {
mv ${gpg_keyring_path} /etc/apt/trusted.gpg.d/isc-kea-2-0.gpg
}
echo_okfail "$text" || die "Could not import the GPG key for this repository"
}
function setup_repository {
local repo_path="/etc/apt/sources.list.d/isc-kea-2-0.list"
check_fetch_config
local text="Installing 'isc/kea-2-0' repository via apt ..."
echo_running "$text"
fetch_config > "$repo_path"
echo_okfail "$text" || die "Could not install the repository, do you have permissions?"
}
function usage () {
cat <<EOF
Usage: $self [opts]
-h Displays this usage text.
-i Ignore repository setup errors during setup and
continue with install. This will leave the repository config
in place rather than removing it upon errors.
EOF
exit 0
}
trap cleanup EXIT
trap shutdown INT
ignore_errors=1
apt_updated="no"
while getopts ":ih" OPT; do
case $OPT in
i) ignore_errors=0 ;;
h) usage ;;
\?) usage ;;
esac
done
shift $(($OPTIND - 1))
#
# MAIN
#
echo "Executing the setup script for the 'isc/kea-2-0' repository ..."
echo
check_tool "curl"
check_tool "apt-get"
detect_os
install_apt_prereqs
import_gpg_key
setup_repository
update_apt
echo_okfail_rc "0" "The repository has been installed successfully - You're ready to rock!"

View File

@ -0,0 +1,515 @@
#!/bin/bash
#####################################################################
####### Script instalador Ogclient
####### Autor: Luis Gerardo Romero <lguillen@unizar.es>
#####################################################################
function globalSetup() {
local current_dir
current_dir=$(dirname "$0")
PROGRAMDIR=$(readlink -e "$current_dir")
PROGRAMNAME=$(basename "$0")
OPENGNSYS_CLIENT_USER="ogdhcp"
# Comprobar si se ha descargado el paquete comprimido (REMOTE=0) o sólo el instalador (REMOTE=1).
if [ -d "$PROGRAMDIR/../installer" ]; then
echo "REMOTE=0"
REMOTE=0
else
echo "REMOTE=1"
REMOTE=1
fi
BRANCH=${1:-"main"}
GIT_REPO="ssh://git@ognproject.evlt.uma.es:21987/opengnsys/ogdhcp.git"
# Directorios de instalación y destino de OpenGnsys.
WORKDIR=/tmp/ogdhcp_installer
INSTALL_TARGET=/opt/ogdhcp
PATH=$PATH:$INSTALL_TARGET/bin
if command -v service &>/dev/null; then
STARTSERVICE="eval service \$service restart"
STOPSERVICE="eval service \$service stop"
else
STARTSERVICE="eval /etc/init.d/\$service restart"
STOPSERVICE="eval /etc/init.d/\$service stop"
fi
ENABLESERVICE="eval update-rc.d \$service defaults"
DISABLESERVICE="eval update-rc.d \$service disable"
APACHESERV=apache2
APACHECFGDIR=/etc/apache2
APACHESITESDIR=sites-available
APACHEOGSITE=ogdhcp
APACHEUSER="www-data"
APACHEGROUP="www-data"
APACHEENABLEMODS="a2enmod headers ssl rewrite proxy_fcgi fastcgi actions alias"
APACHEENABLESSL="a2ensite default-ssl"
APACHEENABLEOG="a2ensite $APACHEENABLEOG"
APACHEMAKECERT="make-ssl-cert generate-default-snakeoil --force-overwrite"
PHPFPMSERV=php7.2-fpm
# Registro de incidencias.
OGLOGFILE="$INSTALL_TARGET/var/log/${PROGRAMNAME%.sh}.log"
LOG_FILE="/tmp/$(basename "$OGLOGFILE")"
}
function checkDependencies() {
echoAndLog "Checking dependencies..."
# Lista de dependencias
local DEPENDENCIES=(
php
php-cli
php-fpm
php-json
php-pdo
php-mysql
php-zip
php-gd
php-mbstring
php-curl
php-xml
php-pear
php-bcmath
composer
unzip
apache2
libapache2-mod-php
kea-dhcp4-server
kea-common
kea-ctrl-agent
jq
net-tools
)
# Comprobar cada dependencia
for dep in "${DEPENDENCIES[@]}"; do
if ! dpkg -s "$dep" >/dev/null 2>&1; then
echoAndLog "$dep is not installed. Installing..."
sudo apt-get install -y "$dep"
else
echoAndLog "$dep is already installed."
fi
done
echoAndLog "Dependencies checked."
}
# Función para instalar los paquetes necesarios para KEA-DHCP
install_kea() {
sudo apt-get install -y isc-kea-common isc-kea-ctrl-agent isc-kea-dhcp4-server isc-kea-dhcp6-server isc-kea-admin
}
# Función para instalar Composer
install_composer() {
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
}
# Función para instalar Swagger UI
install_swagger() {
sudo apt-get install -y unzip
wget https://github.com/swagger-api/swagger-ui/archive/master.zip
unzip master.zip -d /var/www/html/
sudo mv /var/www/html/swagger-ui-master /var/www/html/swagger-ui
}
# Obtiene el código fuente del proyecto desde el repositorio de GitHub.
function downloadCode() {
if [ $# -ne 1 ]; then
errorAndLog "${FUNCNAME}(): invalid number of parameters"
exit 1
fi
local url="$1"
echoAndLog "${FUNCNAME}(): downloading code from '$url'..."
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new" git archive --remote="$url" --format zip --output opengnsys.zip --prefix=opengnsys/ "$BRANCH" && unzip opengnsys.zip
if [ $? -ne 0 ]; then
errorAndLog "${FUNCNAME}(): error getting OpenGnsys code from $url"
return 1
fi
rm -f opengnsys.zip
echoAndLog "${FUNCNAME}(): code was downloaded"
return 0
}
# Crea la estructura base de la instalación de opengnsys
function createDirs() {
if [ $# -ne 1 ]; then
errorAndLog "${FUNCNAME}(): invalid number of parameters"
exit 1
fi
local path_opengnsys_base="$1"
# Crear estructura de directorios.
echoAndLog "${FUNCNAME}(): creating directory paths in $path_opengnsys_base"
mkdir -p "$path_opengnsys_base"/{bin,config,docs,public,src,etc/kea/backup,templates,var/{cache,log},vendor}
if [ $? -ne 0 ]; then
errorAndLog "${FUNCNAME}(): error while creating dirs. Do you have write permissions?"
return 1
fi
# Crear usuario ficticio.
if id -u "$OPENGNSYS_CLIENT_USER" &>/dev/null; then
echoAndLog "${FUNCNAME}(): user \"$OPENGNSYS_CLIENT_USER\" is already created"
else
echoAndLog "${FUNCNAME}(): creating OpenGnsys user"
useradd "$OPENGNSYS_CLIENT_USER" 2>/dev/null
if [ $? -ne 0 ]; then
errorAndLog "${FUNCNAME}(): error creating OpenGnsys user"
return 1
fi
fi
# Mover el fichero de registro de instalación al directorio de logs.
echoAndLog "${FUNCNAME}(): moving installation log file"
mv "$LOG_FILE" "$path_opengnsys_base/var/log" && LOG_FILE="$OGLOGFILE"
chmod 777 "$LOG_FILE"
sudo chmod -R 777 "$path_opengnsys_base/etc"
# Mover el fichero de registro de instalación al directorio de logs.
echoAndLog "${FUNCNAME}(): moving installation log file"
touch "$path_opengnsys_base/var/log/dev.log"
chmod 777 "$path_opengnsys_base/var/log/dev.log"
echoAndLog "${FUNCNAME}(): directory paths created"
return 0
# Cambiar permisos de usuario
echoAndLog "Changing user permission"
chown -R "$OPENGNSYS_CLIENT_USER:$OPENGNSYS_CLIENT_USER" "$INSTALL_TARGET"
# Copiar .env
cp -a "$WORKDIR/ogdhcp/.env" "${path_opengnsys_base}/.env"
}
function create_ogdhcp_project {
# Cambia al usuario ogdhcp y crea el proyecto Symfony
local path_opengnsys_base="$1"
composer create-project symfony/website-skeleton "$path_opengnsys_base"
pushd "$path_opengnsys_base" || return
# Elimina el archivo composer.lock
rm composer.lock
popd || return
echoAndLog "Esqueleto de la aplicación creado y archivo composer.lock eliminado."
}
function copyServerFiles() {
if [ $# -ne 1 ]; then
errorAndLog "${FUNCNAME}(): invalid number of parameters"
exit 1
fi
local path_opengnsys_base="$1"
# Lista de ficheros y directorios origen y de directorios destino.
local SOURCES=(
config
#public
src
etc
templates
tests
vendor
.env
composer.json
composer.lock
phpunit.xml.dist
symfony.lock
)
local TARGETS=(
config
#public
src
etc
templates
tests
vendor
.env
composer.json
composer.lock
phpunit.xml.dist
symfony.lock
)
if [ "${#SOURCES[@]}" != "${#TARGETS[@]}" ]; then
errorAndLog "${FUNCNAME}(): inconsistent number of array items"
exit 1
fi
# Copiar ficheros.
echoAndLog "${FUNCNAME}(): copying files to server directories"
pushd "$WORKDIR/ogdhcp" || return
local i
for (( i = 0; i < ${#SOURCES[@]}; i++ )); do
if [ -f "${SOURCES[$i]}" ]; then
echoAndLog "Copying ${SOURCES[$i]} to $path_opengnsys_base/${TARGETS[$i]}"
cp -a "${SOURCES[$i]}" "$path_opengnsys_base/${TARGETS[$i]}"
elif [ -d "${SOURCES[$i]}" ]; then
echoAndLog "Copying content of ${SOURCES[$i]} to $path_opengnsys_base/${TARGETS[$i]}"
cp -a "${SOURCES[$i]}"/* "$path_opengnsys_base/${TARGETS[$i]}"
else
warningAndLog "Unable to copy ${SOURCES[$i]} to $path_opengnsys_base/${TARGETS[$i]}"
fi
done
echoAndLog "Changing user permission"
chown -R "$OPENGNSYS_CLIENT_USER:$OPENGNSYS_CLIENT_USER" "$INSTALL_TARGET"
popd || return
}
function downloadComposer() {
echoAndLog "Downloading composer.phar..."
# Crear el directorio de trabajo si no existe
mkdir -p "$WORKDIR/ogdhcp/bin" || return
# Cambiar al directorio de trabajo
pushd "$WORKDIR/ogdhcp/bin" || return
# Descargar composer.phar
curl -sS https://getcomposer.org/installer | php
# Comprobar si la descarga fue exitosa
if [ ! -f composer.phar ]; then
errorAndLog "Failed to download composer.phar"
popd
return 1
fi
# Crear el directorio de destino si no existe
mkdir -p "/opt/ogdhcp/bin" || return
# Mover composer.phar a /opt/ogdhcp/bin
mv composer.phar "/opt/ogdhcp/bin/"
# Comprobar si el movimiento fue exitoso
if [ ! -f "/opt/ogdhcp/bin/composer.phar" ]; then
errorAndLog "Failed to move composer.phar to /opt/ogdhcp/bin"
popd
return 1
fi
# Volver al directorio original
popd || return
echoAndLog "composer.phar downloaded and moved to /opt/ogdhcp/bin"
return 0
}
function runComposer() {
echoAndLog "Running composer.phar to install dependencies..."
# Cambiar al directorio donde está composer.phar
pushd /opt/ogdhcp/bin || return
# Ejecutar composer.phar
sudo -u "$OPENGNSYS_CLIENT_USER" php composer.phar install
# Comprobar si la ejecución fue exitosa
if [ $? -ne 0 ]; then
errorAndLog "Failed to run composer.phar"
popd
return 1
fi
# Volver al directorio original
popd || return
echoAndLog "composer.phar ran successfully and dependencies were installed"
return 0
}
function install_swagger_ui {
# Define la URL del archivo de Swagger UI que quieres descargar
swagger_ui_url="https://github.com/swagger-api/swagger-ui/archive/refs/heads/master.zip"
# Define la ruta donde quieres descomprimir Swagger UI
swagger_ui_path="/tmp/swagger-ui"
# Define la ruta de destino para los archivos de Swagger UI
destination_path="/opt/ogdhcp/public"
# Crea los directorios si no existen
mkdir -p "$swagger_ui_path"
mkdir -p "$destination_path"
# Descarga el archivo de Swagger UI
wget "$swagger_ui_url" -O /tmp/swagger-ui.zip
# Descomprime el archivo de Swagger UI en la ruta especificada
unzip /tmp/swagger-ui.zip -d "$swagger_ui_path"
# Copia los archivos de Swagger UI al directorio de destino
cp -r "$swagger_ui_path"/swagger-ui-master/dist/* "$destination_path"
# Elimina el archivo descargado y el directorio temporal
rm /tmp/swagger-ui.zip
rm -r "$swagger_ui_path"
/opt/ogdhcp/vendor/bin/openapi /opt/ogdhcp/src/DhcpBundle/Controller/ -o "$destination_path/swagger.json"
echo "Swagger UI instalado en $destination_path."
}
function installWebConsoleApacheConf() {
if [ $# -ne 2 ]; then
errorAndLog "${FUNCNAME}(): invalid number of parameters"
exit 1
fi
local path_opengnsys_base="$1"
local path_apache2_confd="$2"
local OGHDPCDIR="${path_opengnsys_base}/public"
local sockfile
if [ ! -d "$path_apache2_confd" ]; then
errorAndLog "${FUNCNAME}(): path to apache2 conf.d can not found, verify your server installation"
return 1
fi
mkdir -p "$path_apache2_confd/{sites-available,sites-enabled}"
echoAndLog "${FUNCNAME}(): creating apache2 config file.."
# Activar PHP-FPM.
echoAndLog "${FUNCNAME}(): configuring PHP-FPM"
service="$PHPFPMSERV"
$ENABLESERVICE; $STARTSERVICE
sockfile=$(find /run/php -name "php*.sock" -type s -print 2>/dev/null | tail -1)
# Activar módulos de Apache.
$APACHEENABLEMODS
# Generar configuración de consola web a partir del archivo de plantilla.
if [ -n "$sockfile" ]; then
sed -e "s,OGHDPCDIR,$OGHDPCDIR,g" \
-e "s,proxy:fcgi:.*,proxy:unix:${sockfile%% *}|fcgi://localhost\",g" \
"$WORKDIR/ogdhcp/etc/apache.conf.tmpl" > "$path_apache2_confd/$APACHESITESDIR/${APACHEOGSITE}.conf"
else
sed -e "s,OGHDPCDIR,$OGHDPCDIR,g" \
"$WORKDIR/ogdhcp/server/etc/apache.conf.tmpl" > "$path_apache2_confd/$APACHESITESDIR/${APACHEOGSITE}.conf"
fi
$APACHEENABLEOG
if [ $? -ne 0 ]; then
errorAndLog "${FUNCNAME}(): config file can't be linked to apache conf, verify your server installation"
return 1
fi
echoAndLog "${FUNCNAME}(): config file created and linked, restarting apache daemon"
service="$APACHESERV"
$ENABLESERVICE; $STARTSERVICE
return 0
}
#####################################################################
####### Algunas funciones útiles de propósito general:
#####################################################################
# Obtiene la fecha y hora actual en el formato especificado
function getDateTime() {
date "+%Y%m%d-%H%M%S"
}
# Escribe un mensaje en el archivo de registro y lo muestra por pantalla
function echoAndLog() {
local DATETIME=$(getDateTime)
echo "$1"
echo "$DATETIME;$SSH_CLIENT;$1" >> "$LOG_FILE"
}
# Escribe un mensaje de error en el archivo de registro y lo muestra por pantalla
function errorAndLog() {
local DATETIME=$(getDateTime)
echo "ERROR: $1"
echo "$DATETIME;$SSH_CLIENT;ERROR: $1" >> "$LOG_FILE"
}
# Escribe un mensaje de advertencia en el archivo de registro y lo muestra por pantalla
function warningAndLog() {
local DATETIME=$(getDateTime)
echo "Warning: $1"
echo "$DATETIME;$SSH_CLIENT;Warning: $1" >> "$LOG_FILE"
}
##########################################################################
################################main######################################
# Sólo ejecutable por usuario root
if [ "$(whoami)" != 'root' ]; then
echo "ERROR: this program must run under root privileges!!"
exit 1
fi
globalSetup
echoAndLog "OpenGnsys installation begins at $(date)"
mkdir -p $WORKDIR
pushd $WORKDIR
checkDependencies
install_kea
# Si es necesario, descarga el repositorio de código en directorio temporal
if [ $REMOTE -eq 1 ]; then
downloadCode $GIT_REPO
if [ $? -ne 0 ]; then
errorAndLog "Error while getting code from the repository"
exit 1
fi
else
ln -fs "$(dirname $PROGRAMDIR)" ogdhcp
fi
create_ogdhcp_project ${INSTALL_TARGET}
if [ $? -ne 0 ]; then
errorAndLog "Error while creating skeleton directory!"
exit 1
fi
# Arbol de directorios de OpenGnsys.
createDirs ${INSTALL_TARGET}
if [ $? -ne 0 ]; then
errorAndLog "Error while creating directory paths!"
exit 1
fi
# Copiar ficheros de servicios OpenGnsys Server.
copyServerFiles ${INSTALL_TARGET}
if [ $? -ne 0 ]; then
errorAndLog "Error while copying the server files!"
exit 1
fi
downloadComposer
runComposer
install_swagger_ui
# Creando configuración de Apache.
installWebConsoleApacheConf $INSTALL_TARGET $APACHECFGDIR
if [ $? -ne 0 ]; then
errorAndLog "Error configuring Apache for OpenGnsys Admin"
exit 1
fi
sudo apt-get update
# install_kea
# install_php
# install_composer
# install_symfony
# install_swagger
# Ahora puedes clonar e instalar el componente ogDhcp
# git clone <URL del repositorio de ogDhcp>
# cd <directorio de ogDhcp>
# composer install

View File

@ -0,0 +1,16 @@
#!/bin/bash
function purge_php_packages {
PACKAGES=(libapache2-mod-php php php-bcmath php-cli php-cli-prompt php-common php-composer-ca-bundle php-composer-semver php-composer-spdx-licenses php-curl php-fpm php-gd php-json php-json-schema php-mbstring php-mysql php-pear php-psr-log php-symfony-console php-symfony-debug php-symfony-filesystem php-symfony-finder php-symfony-polyfill-mbstring php-symfony-process php-xml php-zip php7.2 php7.2-bcmath php7.2-cli php7.2-common php7.2-curl php7.2-fpm php7.2-gd php7.2-json php7.2-mbstring php7.2-mysql php7.2-opcache php7.2-readline php7.2-xml php7.2-zip)
for PACKAGE in "${PACKAGES[@]}"
do
sudo apt-get purge -y $PACKAGE
done
}
# Call the function
purge_php_packages
# Verify that all PHP packages have been removed
dpkg --get-selections | grep -i php

0
migrations/.gitignore vendored 100644
View File

38
phpunit.xml.dist 100644
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php"
convertDeprecationsToExceptions="false"
>
<php>
<ini name="display_errors" value="1" />
<ini name="error_reporting" value="-1" />
<server name="APP_ENV" value="test" force="true" />
<server name="SHELL_VERBOSITY" value="-1" />
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>
<extensions>
</extensions>
</phpunit>

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

16
public/index.css 100644
View File

@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}

19
public/index.html 100644
View File

@ -0,0 +1,19 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
</body>
</html>

9
public/index.php 100644
View File

@ -0,0 +1,9 @@
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

View File

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

View File

@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "http://192.168.0.27:8080/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

549
public/swagger.json 100644
View File

@ -0,0 +1,549 @@
{
"openapi": "3.0.0",
"info": {
"title": "Ogdhcp API",
"version": "1.0"
},
"paths": {
"/opengnsys3/rest/dhcp/subnets": {
"get": {
"operationId": "8f92a1cbcb8cd176bdc4ae272b3ad303",
"responses": {
"200": {
"description": "Devuelve todas las subredes",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Subnet"
}
}
}
}
},
"400": {
"description": "Error al obtener las subredes"
}
}
},
"post": {
"summary": "Add a new DHCP subnet",
"operationId": "88949bae5e7784ce2721ffafe7c88c0a",
"requestBody": {
"description": "JSON payload",
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"subnetId": {
"type": "integer",
"example": 2
},
"mask": {
"type": "string",
"example": "255.255.255.0"
},
"address": {
"type": "string",
"example": "192.168.1.0"
},
"nextServer": {
"type": "string",
"example": "192.168.1.1"
},
"bootFileName": {
"type": "string",
"example": "pxelinux.0"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Subnet added successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"400": {
"description": "Error occurred",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "string"
}
},
"type": "object"
}
}
}
}
}
}
},
"/opengnsys3/rest/dhcp/subnets/{subnetId}": {
"put": {
"summary": "Modify a DHCP subnet",
"operationId": "548b55fc0e1ad59ea5b5c909dfd07c71",
"parameters": [
{
"name": "subnetId",
"in": "path",
"description": "ID of the subnet to modify",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"description": "Data to modify the subnet",
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"mask": {
"type": "string"
},
"address": {
"type": "string"
},
"nextServer": {
"type": "string"
},
"bootFileName": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Subnet modified successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"400": {
"description": "Error occurred",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "string"
}
},
"type": "object"
}
}
}
}
}
},
"delete": {
"summary": "Delete a DHCP subnet",
"operationId": "c1c1c34729bdd85857b22e6e2bdc41de",
"parameters": [
{
"name": "subnetId",
"in": "path",
"description": "ID of the subnet to delete",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Subnet deleted successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"400": {
"description": "Error occurred",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "string"
}
},
"type": "object"
}
}
}
}
}
}
},
"/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts": {
"get": {
"summary": "Get all hosts in a subnet",
"operationId": "0989ca622a6a7e23f5af8e3bdd6f6b05",
"parameters": [
{
"name": "subnetId",
"in": "path",
"description": "The ID of the subnet",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "List of hosts in the subnet",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Host"
}
}
}
}
},
"400": {
"description": "Error occurred",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"500": {
"description": "Server error",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "string"
}
},
"type": "object"
}
}
}
}
}
},
"put": {
"summary": "Update a DHCP host",
"operationId": "1541441cd53685aaf6df45ab48befaa8",
"parameters": [
{
"name": "subnetId",
"in": "path",
"description": "The ID of the subnet",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"description": "Data for the host to update",
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"host": {
"type": "string",
"example": "pc11"
},
"oldMacAddress": {
"type": "string",
"example": "56:6f:c7:4f:00:4f"
},
"oldAddress": {
"type": "string",
"example": "192.168.1.11"
},
"macAddress": {
"type": "string",
"example": "56:6f:c7:4f:01:01"
},
"address": {
"type": "string",
"example": "192.168.1.11"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Host updated successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"400": {
"description": "Error occurred",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "string"
}
},
"type": "object"
}
}
}
}
}
},
"post": {
"summary": "Add a DHCP host to a subnet",
"operationId": "3f897dcd7c04787ac9c42ddbb57cb800",
"parameters": [
{
"name": "subnetId",
"in": "path",
"description": "ID of the subnet to add the host to",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"description": "Data for the new host",
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"host": {
"type": "string",
"example": "pc11"
},
"macAddress": {
"type": "string",
"example": "56:6f:c7:4f:00:4f"
},
"address": {
"type": "string",
"example": "172.30.4.11"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Host added successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"400": {
"description": "Error occurred",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "string"
}
},
"type": "object"
}
}
}
}
}
},
"delete": {
"summary": "Delete a DHCP host from a specific subnet",
"operationId": "f652c52b39d57d283401df66a7930b5f",
"parameters": [
{
"name": "subnetId",
"in": "path",
"description": "The ID of the subnet",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"description": "Data for the host to delete",
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"host": {
"type": "string",
"example": "pc11"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Host deleted successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"400": {
"description": "Error occurred",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "string"
}
},
"type": "object"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Subnet": {
"properties": {
"id": {
"description": "The ID of the subnet",
"type": "integer"
},
"subnet": {
"description": "The name of the subnet",
"type": "string"
},
"next-server": {
"description": "The next server in the subnet",
"type": "string"
},
"boot-file-name": {
"description": "The boot file name for the subnet",
"type": "string"
},
"reservations": {
"type": "array",
"items": {
"description": "The reservations in the subnet",
"type": "object"
}
}
},
"type": "object"
},
"Host": {
"properties": {
"host": {
"type": "string",
"example": "pc11"
},
"macAddress": {
"type": "string",
"example": "56:6f:c7:4f:00:4f"
},
"address": {
"type": "string",
"example": "172.30.4.11"
}
},
"type": "object"
}
}
}
}

View File

View File

@ -0,0 +1,876 @@
<?php
// src/DhcpBundle/Controller/DhcpController.php
namespace App\DhcpBundle\Controller;
use OpenApi\Annotations as OA;
use Psr\Log\LoggerInterface;
use App\DhcpBundle\Service\CurlKeaService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Exception;
class DhcpController
{
private $logger;
/**
* @OA\Info(title="Ogdhcp API", version="1.0")
*/
private $curlKeaService;
public function __construct(CurlKeaService $curlKeaService, LoggerInterface $logger)
{
$this->curlKeaService = $curlKeaService;
$this->logger = $logger;
}
/**
*
* @OA\Schema(
* schema="Subnet",
* type="object",
* @OA\Property(property="id", type="integer", description="The ID of the subnet"),
* @OA\Property(property="subnet", type="string", description="The name of the subnet"),
* @OA\Property(property="next-server", type="string", description="The next server in the subnet"),
* @OA\Property(property="boot-file-name", type="string", description="The boot file name for the subnet"),
* @OA\Property(
* property="reservations",
* type="array",
* @OA\Items(type="object", description="The reservations in the subnet")
* )
* )
* @OA\Get(
* path="/opengnsys3/rest/dhcp/subnets",
* @OA\Response(
* response=200,
* description="Devuelve todas las subredes",
* @OA\JsonContent(
* type="array",
* @OA\Items(ref="#/components/schemas/Subnet")
* )
* ),
* @OA\Response(
* response=400,
* description="Error al obtener las subredes",
* )
* )
* @Route("/opengnsys3/rest/dhcp/subnets", methods={"GET"})
*/
public function getSubnets(): JsonResponse
{
try {
$response = $this->curlKeaService->executeCurlCommand('config-get');
if (!$response) {
$responseError = 'Error: No se pudo acceder al archivo dhcpd.conf';
return new JsonResponse(['error' => $responseError], 400);
} else {
$result_code = $response[0]["result"];
if ($result_code == 0) {
if (!isset($response[0]['arguments']['Dhcp4']['subnet4'])) {
$responseError = 'El campo \'subnet4\' no está inicializado';
return new JsonResponse(['error' => $responseError], 400);
} else {
$arrayReservations = $response[0]['arguments']['Dhcp4']['subnet4'];
return new JsonResponse($arrayReservations, 200);
}
} else {
$responseError = "Error kea configuration invalid: " . $response[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
}
}
} catch (Exception $e) {
$responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
/**
* @OA\Post(
* path="/opengnsys3/rest/dhcp/subnets",
* summary="Add a new DHCP subnet",
* @OA\RequestBody(
* description="JSON payload",
* required=true,
* @OA\JsonContent(
* type="object",
* @OA\Property(property="subnetId", type="integer", example=2),
* @OA\Property(property="mask", type="string", example="255.255.255.0"),
* @OA\Property(property="address", type="string", example="192.168.1.0"),
* @OA\Property(property="nextServer", type="string", example="192.168.1.1"),
* @OA\Property(property="bootFileName", type="string", example="pxelinux.0")
* )
* ),
* @OA\Response(
* response=200,
* description="Subnet added successfully",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="string")
* )
* ),
* @OA\Response(
* response=400,
* description="Error occurred",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string")
* )
* )
* )
* @Route("/opengnsys3/rest/dhcp/subnets", methods={"POST"})
*/
public function addDhcpSubnet(Request $request): JsonResponse
{
try {
$input = json_decode($request->getContent());
$subnetId = (int) htmlspecialchars($input->subnetId);
$mask = htmlspecialchars($input->mask);
$address = htmlspecialchars($input->address);
$nextServer = htmlspecialchars($input->nextServer);
$bootFileName = htmlspecialchars($input->bootFileName);
} catch (Exception $e) {
$response["message"] = $e->getMessage();
return new JsonResponse(['error' => $response], 400);
}
try {
$response = $this->curlKeaService->executeCurlCommand('config-get');
$subnetName = $address . '/' . $this->curlKeaService->convertMaskToCIDR($mask);
$newSubnet = [
"id" => $subnetId,
"subnet" => $subnetName,
"next-server" => $nextServer,
"boot-file-name" => $bootFileName,
"reservations" => []
];
if (!isset($response[0]['arguments']['Dhcp4']['subnet4'])) {
$response[0]['arguments']['Dhcp4']['subnet4'] = [];
}
$exists = array_reduce($response[0]['arguments']['Dhcp4']['subnet4'], function ($exists, $subnetElement) use ($subnetName, $subnetId) {
return $exists || ($subnetElement['subnet'] === $subnetName) || ($subnetElement['id'] === $subnetId);;
});
if ($exists) {
$responseError = "Error: La subred el subnet '$subnetName' ya existe en las subredes";
return new JsonResponse(['error' => $responseError], 400);
} else {
$response[0]['arguments']['Dhcp4']['subnet4'][] = $newSubnet;
$array_encoded = json_encode($response[0]['arguments']);
$configurationParsed = str_replace('\\', '', $array_encoded);
$configuration = json_decode($configurationParsed);
$responseTest = $this->curlKeaService->executeCurlCommand('config-test', $configuration);
if ($responseTest[0]["result"] == 0) {
$responseSet = $this->curlKeaService->executeCurlCommand('config-set', $configuration);
if ($responseSet == false || $responseSet[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseWrite = $this->curlKeaService->executeCurlCommand('config-write', $configuration);
if ($responseWrite == false || $responseWrite[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseWrite[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseSuccess = "Configuración cargada correctamente";
return new JsonResponse(['success' => $responseSuccess], 200);
}
}
} else {
$responseError = "Error kea configuration invalid: " . $responseTest[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
}
}
} catch (Exception $e) {
$responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
/**
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}", methods={"DELETE"})
* @OA\Delete(
* path="/opengnsys3/rest/dhcp/subnets/{subnetId}",
* summary="Delete a DHCP subnet",
* @OA\Parameter(
* name="subnetId",
* in="path",
* description="ID of the subnet to delete",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\Response(
* response=200,
* description="Subnet deleted successfully",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="string")
* )
* ),
* @OA\Response(
* response=400,
* description="Error occurred",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string")
* )
* )
* )
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}", methods={"DELETE"})
*/
public function deleteDhcpSubnet(Request $request): JsonResponse
{
$subnetId = (int) $request->get('subnetId');
try {
$response = $this->curlKeaService->executeCurlCommand('config-get');
if (!isset($response[0]['arguments']['Dhcp4']['subnet4'])) {
$responseError = "Error: No hay subredes definidas";
return new JsonResponse(['error' => $responseError], 400);
}
$subnetIndex = array_search($subnetId, array_column($response[0]['arguments']['Dhcp4']['subnet4'], 'id'));
if ($subnetIndex === false) {
$responseError = "Error: La subred con el id '$subnetId' no existe";
return new JsonResponse(['error' => $responseError], 400);
} else {
unset($response[0]['arguments']['Dhcp4']['subnet4'][$subnetIndex]);
$response[0]['arguments']['Dhcp4']['subnet4'] = array_values($response[0]['arguments']['Dhcp4']['subnet4']);
$array_encoded = json_encode($response[0]['arguments']);
$configurationParsed = str_replace('\\', '', $array_encoded);
$configuration = json_decode($configurationParsed);
$responseTest = $this->curlKeaService->executeCurlCommand('config-test', $configuration);
if ($responseTest[0]["result"] == 0) {
$responseSet = $this->curlKeaService->executeCurlCommand('config-set', $configuration);
if ($responseSet == false || $responseSet[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseWrite = $this->curlKeaService->executeCurlCommand('config-write', $configuration);
if ($responseWrite == false || $responseWrite[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseWrite[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseSuccess = "Subred eliminada correctamente";
return new JsonResponse(['success' => $responseSuccess], 200);
}
}
} else {
$responseError = "Error kea configuration invalid: " . $responseTest[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
}
}
} catch (Exception $e) {
$responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
/**
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}", methods={"PUT"})
* @OA\Put(
* path="/opengnsys3/rest/dhcp/subnets/{subnetId}",
* summary="Modify a DHCP subnet",
* @OA\Parameter(
* name="subnetId",
* in="path",
* description="ID of the subnet to modify",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\RequestBody(
* description="Data to modify the subnet",
* required=true,
* @OA\JsonContent(
* type="object",
* @OA\Property(property="mask", type="string"),
* @OA\Property(property="address", type="string"),
* @OA\Property(property="nextServer", type="string"),
* @OA\Property(property="bootFileName", type="string")
* )
* ),
* @OA\Response(
* response=200,
* description="Subnet modified successfully",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="string")
* )
* ),
* @OA\Response(
* response=400,
* description="Error occurred",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string")
* )
* )
* )
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}", methods={"PUT"})
*/
public function modifyDhcpSubnet(Request $request): JsonResponse
{
$subnetId = (int) $request->get('subnetId');
try {
$input = json_decode($request->getContent());
$mask = htmlspecialchars($input->mask);
$address = htmlspecialchars($input->address);
$nextServer = htmlspecialchars($input->nextServer);
$bootFileName = htmlspecialchars($input->bootFileName);
} catch (Exception $e) {
$response["message"] = $e->getMessage();
return new JsonResponse(['error' => $response], 400);
}
try {
$response = $this->curlKeaService->executeCurlCommand('config-get');
$subnetName = $address . '/' . $this->curlKeaService->convertMaskToCIDR($mask);
if (!isset($response[0]['arguments']['Dhcp4']['subnet4'])) {
$responseError = "Error: No hay subredes definidas";
return new JsonResponse(['error' => $responseError], 400);
}
$subnetIndex = array_search($subnetId, array_column($response[0]['arguments']['Dhcp4']['subnet4'], 'id'));
if ($subnetIndex === false) {
$responseError = "Error: La subred con el id '$subnetId' no existe";
return new JsonResponse(['error' => $responseError], 400);
} else {
$response[0]['arguments']['Dhcp4']['subnet4'][$subnetIndex] = [
"id" => $subnetId,
"subnet" => $subnetName,
"next-server" => $nextServer,
"boot-file-name" => $bootFileName,
"reservations" => []
];
$array_encoded = json_encode($response[0]['arguments']);
$configurationParsed = str_replace('\\', '', $array_encoded);
$configuration = json_decode($configurationParsed);
$responseTest = $this->curlKeaService->executeCurlCommand('config-test', $configuration);
if ($responseTest[0]["result"] == 0) {
$responseSet = $this->curlKeaService->executeCurlCommand('config-set', $configuration);
if ($responseSet == false || $responseSet[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseWrite = $this->curlKeaService->executeCurlCommand('config-write', $configuration);
if ($responseWrite == false || $responseWrite[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseWrite[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseSuccess = "Subred modificada correctamente";
return new JsonResponse(['success' => $responseSuccess], 200);
}
}
} else {
$responseError = "Error kea configuration invalid: " . $responseTest[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
}
}
} catch (Exception $e) {
$responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
/**
* @OA\Schema(
* schema="Host",
* type="object",
* @OA\Property(property="host", type="string", example="pc11"),
* @OA\Property(property="macAddress", type="string", example="56:6f:c7:4f:00:4f"),
* @OA\Property(property="address", type="string", example="172.30.4.11")
* )
* @OA\Get(
* path="/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts",
* summary="Get all hosts in a subnet",
* @OA\Parameter(
* name="subnetId",
* in="path",
* description="The ID of the subnet",
* required=true,
* @OA\Schema(type="integer")
* ),
* @OA\Response(
* response=200,
* description="List of hosts in the subnet",
* @OA\JsonContent(
* type="array",
* @OA\Items(ref="#/components/schemas/Host")
* )
* ),
* @OA\Response(
* response=400,
* description="Error occurred",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string")
* )
* ),
* @OA\Response(
* response=500,
* description="Server error",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string")
* )
* )
* )
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts", methods={"GET"})
*/
public function getHosts($subnetId): JsonResponse
{
try {
$response = $this->curlKeaService->executeCurlCommand('config-get');
if (!$response) {
$responseError = 'Error: No se pudo acceder al archivo dhcpd.conf';
return new JsonResponse(['error' => $responseError], 400);
} else {
$result_code = $response[0]["result"];
if ($result_code == 0) {
if (!isset($response[0]['arguments']['Dhcp4']['subnet4'])) {
$responseError = 'El campo \'subnet4\' no está inicializado';
return new JsonResponse(['error' => $responseError], 400);
} else {
$subnets = $response[0]['arguments']['Dhcp4']['subnet4'];
foreach ($subnets as $subnet) {
if ($subnet['id'] == $subnetId) {
return new JsonResponse($subnet['reservations'], 200);
}
}
$responseError = 'Error: La subred con el id \'' . $subnetId . '\' no existe.';
return new JsonResponse(['error' => $responseError], 400);
}
} else {
$responseError = "Error kea configuration invalid: " . $response[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
}
}
} catch (Exception $e) {
$responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
/**
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts", methods={"POST"})
* @OA\Post(
* path="/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts",
* summary="Add a DHCP host to a subnet",
* @OA\Parameter(
* name="subnetId",
* in="path",
* description="ID of the subnet to add the host to",
* required=true,
* @OA\Schema(
* type="integer"
* )
* ),
* @OA\RequestBody(
* description="Data for the new host",
* required=true,
* @OA\JsonContent(
* type="object",
* @OA\Property(property="host", type="string", example="pc11"),
* @OA\Property(property="macAddress", type="string", example="56:6f:c7:4f:00:4f"),
* @OA\Property(property="address", type="string", example="172.30.4.11")
* )
* ),
* @OA\Response(
* response=200,
* description="Host added successfully",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="string")
* )
* ),
* @OA\Response(
* response=400,
* description="Error occurred",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string")
* )
* )
* )
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts", methods={"POST"})
*/
public function addDhcpHost(Request $request): JsonResponse
{
$subnetId = (int) $request->get('subnetId');
try {
$input = json_decode($request->getContent());
$host = htmlspecialchars($input->host);
$macAddress = htmlspecialchars($input->macAddress);
$address = htmlspecialchars($input->address);
} catch (Exception $e) {
$response["message"] = $e->getMessage();
return new JsonResponse(['error' => $response], 400);
}
try {
$response = $this->curlKeaService->executeCurlCommand('config-get');
$newHost = [
"hostname" => $host,
"hw-address" => $macAddress,
"ip-address" => $address
];
$subnetFound = false;
foreach ($response[0]['arguments']['Dhcp4']['subnet4'] as &$subnet) {
if ($subnet['id'] == $subnetId) {
$subnetFound = true;
if (!isset($subnet['reservations'])) {
$subnet['reservations'] = [];
}
$exists = array_reduce($subnet['reservations'], function ($exists, $reservation) use ($host) {
return $exists || ($reservation['hostname'] === $host);
});
if ($exists) {
$responseError = "Error: El host con el hostname '$host' ya existe en las reservaciones.";
return new JsonResponse(['error' => $responseError], 400);
} else {
$subnet['reservations'][] = $newHost;
break;
}
}
}
if (!$subnetFound) {
$responseError = "Error: No se encontró la subnet con id '$subnetId'.";
return new JsonResponse(['error' => $responseError], 400);
}
$array_encoded = json_encode($response[0]['arguments']);
$configurationParsed = str_replace('\\', '', $array_encoded);
$configuration = json_decode($configurationParsed);
$responseTest = $this->curlKeaService->executeCurlCommand('config-test', $configuration);
if ($responseTest[0]["result"] == 0) {
$responseSet = $this->curlKeaService->executeCurlCommand('config-set', $configuration);
if ($responseSet == false || $responseSet[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseWrite = $this->curlKeaService->executeCurlCommand('config-write', $configuration);
if ($responseWrite == false || $responseWrite[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseWrite[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseSuccess = "Configuración cargada correctamente";
return new JsonResponse(['success' => $responseSuccess], 200);
}
}
} else {
$responseError = "Error kea configuration invalid: " . $responseTest[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
}
} catch (Exception $e) {
$responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
/**
* @OA\Delete(
* path="/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts",
* summary="Delete a DHCP host from a specific subnet",
* @OA\Parameter(
* name="subnetId",
* in="path",
* description="The ID of the subnet",
* required=true,
* @OA\Schema(type="integer")
* ),
* @OA\RequestBody(
* description="Data for the host to delete",
* required=true,
* @OA\JsonContent(
* type="object",
* @OA\Property(property="host", type="string", example="pc11")
* )
* ),
* @OA\Response(
* response=200,
* description="Host deleted successfully",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="string")
* )
* ),
* @OA\Response(
* response=400,
* description="Error occurred",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string")
* )
* )
* )
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts", methods={"DELETE"})
*/
public function deleteDhcpHost(Request $request, $subnetId): JsonResponse
{
try {
$input = json_decode($request->getContent());
$host = htmlspecialchars($input->host);
} catch (Exception $e) {
$response["message"] = $e->getMessage();
return new JsonResponse(['error' => $response], 400);
}
try {
$response = $this->curlKeaService->executeCurlCommand('config-get');
$subnetFound = false;
foreach ($response[0]['arguments']['Dhcp4']['subnet4'] as &$subnet) {
if ($subnet['id'] == $subnetId) {
$subnetFound = true;
if (!isset($subnet['reservations'])) {
$subnet['reservations'] = [];
}
foreach ($subnet['reservations'] as $key => $reservation) {
if (isset($reservation['hostname']) && $reservation['hostname'] === $host) {
unset($subnet['reservations'][$key]);
$subnet['reservations'] = array_values($subnet['reservations']);
break;
}
}
}
}
if ($subnetFound) {
$array_encoded = json_encode($response[0]['arguments']);
$configurationParsed = str_replace('\\', '', $array_encoded);
$configuration = json_decode($configurationParsed);
$responseTest = $this->curlKeaService->executeCurlCommand('config-test', $configuration);
if ($responseTest[0]["result"] == 0) {
$responseSet = $this->curlKeaService->executeCurlCommand('config-set', $configuration);
if ($responseSet == false || $responseSet[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseWrite = $this->curlKeaService->executeCurlCommand('config-write', $configuration);
if ($responseWrite == false || $responseWrite[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseWrite[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseSuccess = "Configuración cargada correctamente";
return new JsonResponse(['success' => $responseSuccess], 200);
}
}
} else {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseTest[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
}
} else {
$responseError = "Error: El host con el hostname '$host' no existe en las reservaciones.";
return new JsonResponse(['error' => $responseError], 400);
}
} catch (Exception $e) {
$responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
/**
* @OA\Put(
* path="/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts",
* summary="Update a DHCP host",
* @OA\Parameter(
* name="subnetId",
* in="path",
* description="The ID of the subnet",
* required=true,
* @OA\Schema(type="integer")
* ),
* @OA\RequestBody(
* description="Data for the host to update",
* required=true,
* @OA\JsonContent(
* type="object",
* @OA\Property(property="host", type="string", example="pc11"),
* @OA\Property(property="oldMacAddress", type="string", example="56:6f:c7:4f:00:4f"),
* @OA\Property(property="oldAddress", type="string", example="192.168.1.11"),
* @OA\Property(property="macAddress", type="string", example="56:6f:c7:4f:01:01"),
* @OA\Property(property="address", type="string", example="192.168.1.11")
* )
* ),
* @OA\Response(
* response=200,
* description="Host updated successfully",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="string")
* )
* ),
* @OA\Response(
* response=400,
* description="Error occurred",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="error", type="string")
* )
* )
* )
*
* @Route("/opengnsys3/rest/dhcp/subnets/{subnetId}/hosts", methods={"PUT"})
*/
public function updateDhcpHost(Request $request, $subnetId): JsonResponse
{
try {
$input = json_decode($request->getContent());
$host = htmlspecialchars($input->host);
$oldMacAddress = htmlspecialchars($input->oldMacAddress);
$oldAddress = htmlspecialchars($input->oldAddress);
$macAddress = htmlspecialchars($input->macAddress);
$address = htmlspecialchars($input->address);
} catch (Exception $e) {
$response["message"] = $e->getMessage();
return new JsonResponse(['error' => $response], 400);
}
try {
$response = $this->curlKeaService->executeCurlCommand('config-get');
$subnetFound = false;
$hostFound = false;
foreach ($response[0]['arguments']['Dhcp4']['subnet4'] as &$subnet) {
if ($subnet['id'] == $subnetId) {
$this->logger->info('FOUND SUBNET');
$subnetFound = true;
if (!isset($subnet['reservations'])) {
$subnet['reservations'] = [];
}
foreach ($subnet['reservations'] as &$reservation) {
$this->logger->info('LOOKING FOR HOST');
if ($reservation['hw-address'] == $oldMacAddress && $reservation['ip-address'] == $oldAddress) {
$this->logger->info('FOUND HOST');
$hostFound = true;
$reservation['hw-address'] = $macAddress;
$reservation['ip-address'] = $address;
$reservation['hostname'] = $host;
break;
}
}
}
}
if ($subnetFound && $hostFound) {
$this->logger->info('UPDATING HOST');
$array_encoded = json_encode($response[0]['arguments']);
$configurationParsed = str_replace('\\', '', $array_encoded);
$configuration = json_decode($configurationParsed);
$responseTest = $this->curlKeaService->executeCurlCommand('config-test', $configuration);
if ($responseTest[0]["result"] == 0) {
$responseSet = $this->curlKeaService->executeCurlCommand('config-set', $configuration);
if ($responseSet == false || $responseSet[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseSet[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseWrite = $this->curlKeaService->executeCurlCommand('config-write', $configuration);
if ($responseWrite == false || $responseWrite[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseWrite[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseSuccess = "Configuración cargada correctamente";
return new JsonResponse(['success' => $responseSuccess], 200);
}
}
} else {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseTest[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
}
} elseif (!$subnetFound) {
$responseError = "Error: La subred con el id '$subnetId' no existe.";
return new JsonResponse(['error' => $responseError], 400);
} elseif (!$hostFound) {
$responseError = "Error: La IP " . $oldAddress . " y la MAC " . $oldMacAddress . " no existe en las reservaciones.";
return new JsonResponse(['error' => $responseError], 400);
}
} catch (Exception $e) {
$responseError = "Error al obtener la configuración de Kea DHCP: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
/**
* @Route("/opengnsys3/rest/dhcp/backup", methods={"POST"})
*/
public function restoreDhcpConfiguration(): JsonResponse
{
$backup_dir = '/opt/opengnsys/etc/kea/backup';
try {
$backup_files = glob($backup_dir . '/*.conf');
if (empty($backup_files)) {
$response = "No se encontraron archivos de backup";
return new JsonResponse(['error' => $response], 400);
} else {
usort($backup_files, function ($a, $b) {
return filemtime($b) - filemtime($a);
});
$backup_file = reset($backup_files);
$config = file_get_contents($backup_file);
$configuration = json_decode($config);
$test_command = 'config-test';
$test_output = $this->curlKeaService->executeCurlCommand($test_command, $configuration);
if ($test_output == false || $test_output[0]["result"] != 0) {
$responseError = "Error al comprobar la configuración de Kea: " . $test_output[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$set_command = 'config-set';
$set_output = $this->curlKeaService->executeCurlCommand($set_command, $configuration, false);
if ($set_output == false || $set_output[0]["result"] != 0) {
$responseError = "Error al guardar la última configuración de Kea: " . $set_output[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
unlink($backup_file);
$responseWrite = $this->curlKeaService->executeCurlCommand('config-write', $configuration);
if ($responseWrite == false || $responseWrite[0]["result"] != 0) {
$responseError = "Error al guardar la configuración en Kea DHCP: " . $responseWrite[0]["text"];
return new JsonResponse(['error' => $responseError], 400);
} else {
$responseSuccess = "Configuración cargada correctamente";
return new JsonResponse(['success' => $responseSuccess], 200);
}
}
}
}
} catch (Exception $e) {
$responseError = "Error al restaurar la configuración: " . $e->getMessage();
return new JsonResponse(['error' => $responseError], 400);
}
}
}

View File

@ -0,0 +1,10 @@
<?php
// src/DhcpBundle/DhcpBundle.php
namespace App\DhcpBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class DhcpBundle extends Bundle
{
}

View File

@ -0,0 +1,103 @@
<?php
// src/DhcpBundle/Service/CurlKeaService.php
namespace App\DhcpBundle\Service;
use Exception;
class CurlKeaService
{
public function executeCurlCommand($command, $arguments = null, $create_backup = true)
{
$apiUrl = getenv('DHCP_IP');
if (!$apiUrl) {
$apiUrl = 'http://localhost:8000/';
}
if ($command == 'config-get' || $command == 'config-write') {
$requestData = array(
'command' => $command,
'service' => array('dhcp4'),
);
} elseif ($command == 'config-set' || $command == 'config-test') {
$requestData = array(
'command' => $command,
'service' => array('dhcp4'),
'arguments' => $arguments,
);
} else {
return "Error: Comando no válido";
}
if (($command == 'config-set' || $command == 'config-write') && $create_backup) {
$this->backupConfig();
}
$jsonData = json_encode($requestData);
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'Expect:'));
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_POST, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$output = json_decode($response, true);
if ($httpCode >= 200 && $httpCode < 300) {
return $output;
} else {
$error = curl_error($ch);
throw new Exception($error);
//return false;
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
public function convertMaskToCIDR($mask)
{
$bits = 0;
$mask = explode(".", $mask);
foreach ($mask as $octect)
$bits += strlen(str_replace("0", "", decbin($octect)));
return $bits;
}
function backupConfig()
{
$get_command = 'config-get';
$get_output = $this->executeCurlCommand($get_command);
if ($get_output == false || $get_output[0]["result"] != 0) {
throw new Exception('Error al obtener la configuración actual de Kea DHCP');
}
$config_text = json_encode($get_output[0]['arguments']);
$configurationParsed = str_replace('\\', '', $config_text);
$backup_dir = __DIR__ . '/../../../etc/kea/backup';
if (!is_dir($backup_dir)) {
throw new Exception('El directorio de backup no existe');
}
if (!is_writable($backup_dir)) {
throw new Exception('El directorio de backup no tiene permisos de escritura');
}
$backup_files = glob($backup_dir . '/*.conf');
if (count($backup_files) >= 10) {
usort($backup_files, function ($a, $b) {
return filemtime($a) - filemtime($b);
});
unlink($backup_files[0]);
}
$backup_file = $backup_dir . '/' . date('Y-m-d_H-i-s') . '.conf';
if (!file_put_contents($backup_file, $configurationParsed)) {
throw new Exception('Error al guardar la configuración de backup');
}
}
}

0
src/Entity/.gitignore vendored 100644
View File

11
src/Kernel.php 100644
View File

@ -0,0 +1,11 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}

0
src/Repository/.gitignore vendored 100644
View File

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
{# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,20 @@
{% extends 'base.html.twig' %}
{% block title %}Hello DhcpController!{% endblock %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper">
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code><a href="{{ '/home/luis/dhcp_symfony/src/Controller/DhcpController.php'|file_link(0) }}">src/Controller/DhcpController.php</a></code></li>
<li>Your template at <code><a href="{{ '/home/luis/dhcp_symfony/templates/dhcp/index.html.twig'|file_link(0) }}">templates/dhcp/index.html.twig</a></code></li>
</ul>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
<?php
use Symfony\Component\Dotenv\Dotenv;
require dirname(__DIR__).'/vendor/autoload.php';
if (file_exists(dirname(__DIR__).'/config/bootstrap.php')) {
require dirname(__DIR__).'/config/bootstrap.php';
} elseif (method_exists(Dotenv::class, 'bootEnv')) {
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
}

0
translations/.gitignore vendored 100644
View File