Initial commit
This commit is contained in:
129
README.md
Normal file
129
README.md
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# Puppet FrankenPHP Module
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This Puppet module installs and manages **FrankenPHP** on Debian-based systems (Debian, Ubuntu) using the official `.deb` package.
|
||||||
|
|
||||||
|
It is designed for a **secure, multi-tenant architecture** by managing two distinct components:
|
||||||
|
1. **FrankenPHP (Proxy)**: A main Caddy/FrankenPHP service, installed via `.deb` and managed by `systemd`, which acts as a reverse proxy.
|
||||||
|
2. **Isolated Applications**: Individual PHP applications, each running in its own `frankenphp` process under a dedicated system user. These processes are managed by **Supervisor** for process and user isolation.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
This module has external dependencies (see `metadata.json`):
|
||||||
|
* `puppetlabs/concat`: Used to assemble the main `Caddyfile` from fragments.
|
||||||
|
* `puppetlabs/stdlib`: For data types (`Stdlib::Absolutepath`).
|
||||||
|
* `ajcrowe/supervisord`: Used to manage the isolated application processes.
|
||||||
|
|
||||||
|
You **must** ensure that Supervisor is installed on the target node. This module only manages *configurations* for Supervisor, not its installation.
|
||||||
|
|
||||||
|
```puppet
|
||||||
|
# In your base profile or site.pp
|
||||||
|
include 'supervisor'
|
||||||
|
````
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The architecture is deployed in two steps for each website:
|
||||||
|
|
||||||
|
1. Declare the isolated application (`frankenphp::app`).
|
||||||
|
2. Declare the public-facing vhost that points to it (`frankenphp::vhost`).
|
||||||
|
|
||||||
|
### 1\. Base Installation
|
||||||
|
|
||||||
|
Simply include the main class. This installs the FrankenPHP binary and configures the proxy service (which is empty by default).
|
||||||
|
|
||||||
|
```puppet
|
||||||
|
include 'frankenphp'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2\. Defining an Isolated Application (`frankenphp::app`)
|
||||||
|
|
||||||
|
This defined type creates a dedicated user, a root directory, a specific Caddyfile for the app, and a Supervisor service to run it on a local port.
|
||||||
|
|
||||||
|
```puppet
|
||||||
|
# Declare a 'blog-app' application
|
||||||
|
frankenphp::app { 'blog-app':
|
||||||
|
ensure => present,
|
||||||
|
root_dir => '/var/www/blog',
|
||||||
|
user => 'bloguser',
|
||||||
|
listen_port => '9010', # Ensure this port is free
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This code will:
|
||||||
|
|
||||||
|
* Create the `bloguser` user.
|
||||||
|
* Create the `/var/www/blog` directory (owned by `bloguser`).
|
||||||
|
* Create a Caddyfile for this app (listening on `localhost:9010`).
|
||||||
|
* Create the `/etc/supervisor/conf.d/frankenphp-blog-app.conf` file to launch this process as the `bloguser`.
|
||||||
|
|
||||||
|
### 3\. Exposing the Application (`frankenphp::vhost`)
|
||||||
|
|
||||||
|
This adds an entry to the main proxy `Caddyfile` to route public traffic to the isolated application.
|
||||||
|
|
||||||
|
```puppet
|
||||||
|
# Create the public vhost for blog.example.com
|
||||||
|
frankenphp::vhost { 'blog.example.com':
|
||||||
|
ensure => present,
|
||||||
|
mode => 'proxy',
|
||||||
|
proxy_target => 'localhost:9010', # Must match the app's listen_port
|
||||||
|
extra_config => @(EOT)
|
||||||
|
# Caddy will handle HTTPS automatically.
|
||||||
|
# We add the Host header for the proxy.
|
||||||
|
header_up Host {host}
|
||||||
|
EOT
|
||||||
|
,
|
||||||
|
# Ensure the proxy isn't defined until the app is managed
|
||||||
|
require => Frankenphp::App['blog-app'],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Classes and Defines
|
||||||
|
|
||||||
|
### Class: `frankenphp`
|
||||||
|
|
||||||
|
The main class that orchestrates installation (`frankenphp::install`), proxy configuration (`frankenphp::config`), and the main service (`frankenphp::service`).
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
* `version` (String): The FrankenPHP version to download (e.g., '1.9.1').
|
||||||
|
* `service_ensure` (String): The state of the main service (default: 'running').
|
||||||
|
* `service_enable` (Boolean): Enable the main service on boot (default: true).
|
||||||
|
|
||||||
|
### Define: `frankenphp::app`
|
||||||
|
|
||||||
|
Manages a single isolated PHP application.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
* `root_dir` (Stdlib::Absolutepath): The application's document root.
|
||||||
|
* `user` (String): The username to create for this application.
|
||||||
|
* `listen_port` (String): The local port the application will listen on (e.g., '9010').
|
||||||
|
* `group` (String): The group for the user (default: `$user`).
|
||||||
|
* `ensure` (Enum['present', 'absent']): Whether the application should exist (default: 'present').
|
||||||
|
|
||||||
|
### Define: `frankenphp::vhost`
|
||||||
|
|
||||||
|
Manages an entry (a "site") in the main proxy `Caddyfile`.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
* `server_name` (String): The vhost name (default: `$title`, e.g., 'https://www.google.com/url?sa=E\&source=gmail\&q=blog.example.com').
|
||||||
|
* `mode` (Enum['proxy', 'fastcgi', 'php\_server']): How Caddy should handle this vhost. **For this architecture, always use 'proxy'**.
|
||||||
|
* `proxy_target` (Optional[String]): The reverse proxy target (e.g., 'localhost:9010').
|
||||||
|
* `fpm_socket` (Optional[String]): (For `fastcgi` mode) Path to the FPM socket.
|
||||||
|
* `root_dir` (Stdlib::Absolutepath): (For `php_server`/`fastcgi` modes) Document root.
|
||||||
|
* `extra_config` (String): A raw string of extra Caddy directives for this vhost (for logs, headers, etc.).
|
||||||
|
* `ensure` (Enum['present', 'absent']): Whether the vhost should exist (default: 'present').
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
* This module only installs FrankenPHP via the `.deb` package. It does not support other installation methods or operating systems.
|
||||||
|
* The installation of the `supervisor` service itself is not handled. You must install it via another module or `package` resource.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Apache 2.0
|
||||||
|
|
||||||
|
```
|
||||||
85
manifests/app.pp
Normal file
85
manifests/app.pp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# modules/frankenphp/manifests/app.pp
|
||||||
|
|
||||||
|
define frankenphp::app (
|
||||||
|
Stdlib::Absolutepath $root_dir,
|
||||||
|
String $user,
|
||||||
|
String $listen_port,
|
||||||
|
String $group = $user,
|
||||||
|
Stdlib::Absolutepath $docs_root = '/var/www',
|
||||||
|
String $app_caddyfile_dir = '/etc/frankenphp/sites.d',
|
||||||
|
String $supervisor_conf_dir = '/etc/supervisor/conf.d',
|
||||||
|
Boolean $managed_root_dir = true,
|
||||||
|
Enum['present', 'absent'] $ensure = 'present',
|
||||||
|
) {
|
||||||
|
|
||||||
|
$app_name = $title
|
||||||
|
$app_caddyfile = "${app_caddyfile_dir}/${app_name}.Caddyfile"
|
||||||
|
$supervisor_conf = "${supervisor_conf_dir}/frankenphp-${app_name}.conf"
|
||||||
|
|
||||||
|
exec { "refresh-frankenphp-${app_name}":
|
||||||
|
command => "supervisorctl restart frankenphp-${app_name}",
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
refreshonly => true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if $ensure == 'present' {
|
||||||
|
# Users
|
||||||
|
ensure_resource('group', $group, { ensure => 'present' })
|
||||||
|
ensure_resource('user', $user, {
|
||||||
|
ensure => 'present',
|
||||||
|
gid => $group,
|
||||||
|
shell => '/bin/false',
|
||||||
|
home => "${docs_root}/${user}",
|
||||||
|
system => true,
|
||||||
|
require => Group[$group],
|
||||||
|
})
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
ensure_resource('file', $app_caddyfile_dir, { ensure => 'directory' })
|
||||||
|
if ! $managed_root_dir {
|
||||||
|
ensure_resource('file', $root_dir, {
|
||||||
|
ensure => 'directory',
|
||||||
|
owner => $user,
|
||||||
|
group => $group,
|
||||||
|
mode => '0755',
|
||||||
|
require => User[$user],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
# Caddyfile
|
||||||
|
$caddy_content = epp('frankenphp/app_caddyfile.epp', {
|
||||||
|
listen_port => $listen_port,
|
||||||
|
root_dir => $root_dir,
|
||||||
|
})
|
||||||
|
|
||||||
|
file { $app_caddyfile:
|
||||||
|
ensure => 'file',
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => $caddy_content,
|
||||||
|
notify => Exec["refresh-frankenphp-${app_name}"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Supervisor
|
||||||
|
supervisord::program { "frankenphp-${app_name}":
|
||||||
|
command => "/usr/bin/frankenphp run --config ${app_caddyfile}",
|
||||||
|
priority => '100',
|
||||||
|
user => $user,
|
||||||
|
autorestart => true,
|
||||||
|
autostart => true,
|
||||||
|
startretries => 20,
|
||||||
|
program_environment => {
|
||||||
|
'PATH' => '/bin:/sbin:/usr/bin:/usr/sbin',
|
||||||
|
},
|
||||||
|
require => File[$app_caddyfile]
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
file { $app_caddyfile: ensure => 'absent' }
|
||||||
|
file { $supervisor_conf:
|
||||||
|
ensure => 'absent',
|
||||||
|
notify => Service['supervisor'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
manifests/config.pp
Normal file
36
manifests/config.pp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# modules/frankenphp/manifests/config.pp
|
||||||
|
|
||||||
|
class frankenphp::config (
|
||||||
|
$caddyfile_path = $frankenphp::params::caddyfile_path,
|
||||||
|
$service_name = $frankenphp::params::service_name,
|
||||||
|
) inherits frankenphp::params {
|
||||||
|
|
||||||
|
$caddyfile_dir = dirname($caddyfile_path)
|
||||||
|
ensure_resource('file', $caddyfile_dir, {
|
||||||
|
ensure => 'directory',
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0755',
|
||||||
|
})
|
||||||
|
|
||||||
|
concat { $caddyfile_path:
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
notify => Service[$service_name],
|
||||||
|
require => File[$caddyfile_dir],
|
||||||
|
}
|
||||||
|
|
||||||
|
concat::fragment { 'caddyfile-global-options':
|
||||||
|
target => $caddyfile_path,
|
||||||
|
order => '01',
|
||||||
|
content => @(EOT)
|
||||||
|
# Options globales Caddy (email, etc.)
|
||||||
|
# {
|
||||||
|
# admin off
|
||||||
|
# email admin@example.com
|
||||||
|
# }
|
||||||
|
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
}
|
||||||
18
manifests/init.pp
Normal file
18
manifests/init.pp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# modules/frankenphp/manifests/init.pp
|
||||||
|
|
||||||
|
class frankenphp (
|
||||||
|
String $version = $frankenphp::params::version,
|
||||||
|
String $service_ensure = 'running',
|
||||||
|
Boolean $service_enable = true,
|
||||||
|
) inherits frankenphp::params {
|
||||||
|
|
||||||
|
require supervisord
|
||||||
|
|
||||||
|
contain 'frankenphp::install'
|
||||||
|
contain 'frankenphp::config'
|
||||||
|
contain 'frankenphp::service'
|
||||||
|
|
||||||
|
Class['frankenphp::install'] ->
|
||||||
|
Class['frankenphp::config'] ~>
|
||||||
|
Class['frankenphp::service']
|
||||||
|
}
|
||||||
36
manifests/install.pp
Normal file
36
manifests/install.pp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# modules/frankenphp/manifests/install.pp
|
||||||
|
|
||||||
|
class frankenphp::install (
|
||||||
|
$version = $frankenphp::params::version,
|
||||||
|
$package_name = $frankenphp::params::package_name,
|
||||||
|
$download_url = $frankenphp::params::download_url,
|
||||||
|
$local_deb_path = $frankenphp::params::local_deb_path,
|
||||||
|
) inherits frankenphp::params {
|
||||||
|
|
||||||
|
if ! defined(Package['wget']) {
|
||||||
|
package { 'wget':
|
||||||
|
ensure => installed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { "download-frankenphp-${version}":
|
||||||
|
command => "/usr/bin/wget -q '${download_url}' -O '${local_deb_path}'",
|
||||||
|
creates => $local_deb_path,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
require => Package['wget'],
|
||||||
|
}
|
||||||
|
|
||||||
|
package { $package_name:
|
||||||
|
ensure => 'installed',
|
||||||
|
provider => 'dpkg',
|
||||||
|
source => $local_deb_path,
|
||||||
|
require => Exec["download-frankenphp-${version}"],
|
||||||
|
notify => Exec["cleanup-frankenphp-deb-${version}"],
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { "cleanup-frankenphp-deb-${version}":
|
||||||
|
command => "/bin/rm -f '${local_deb_path}'",
|
||||||
|
path => ['/bin'],
|
||||||
|
refreshonly => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
14
manifests/params.pp
Normal file
14
manifests/params.pp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# modules/frankenphp/manifests/params.pp
|
||||||
|
|
||||||
|
class frankenphp::params {
|
||||||
|
$version = '1.9.1'
|
||||||
|
$package_name = 'frankenphp'
|
||||||
|
$service_name = 'frankenphp'
|
||||||
|
$caddyfile_path = '/etc/frankenphp/Caddyfile'
|
||||||
|
$download_dir = '/tmp'
|
||||||
|
$arch = $facts['architecture']
|
||||||
|
$deb_filename = "frankenphp_${version}-1_${arch}.deb"
|
||||||
|
$local_deb_path = "${download_dir}/${deb_filename}"
|
||||||
|
$download_base_url = 'https://github.com/php/frankenphp/releases/download'
|
||||||
|
$download_url = "${download_base_url}/v${version}/${deb_filename}"
|
||||||
|
}
|
||||||
15
manifests/service.pp
Normal file
15
manifests/service.pp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# modules/frankenphp/manifests/service.pp
|
||||||
|
|
||||||
|
class frankenphp::service (
|
||||||
|
$service_name = $frankenphp::params::service_name,
|
||||||
|
$package_name = $frankenphp::params::package_name,
|
||||||
|
$service_ensure = 'running',
|
||||||
|
$service_enable = true,
|
||||||
|
) inherits frankenphp::params {
|
||||||
|
|
||||||
|
service { $service_name:
|
||||||
|
ensure => $service_ensure,
|
||||||
|
enable => $service_enable,
|
||||||
|
subscribe => Package[$package_name],
|
||||||
|
}
|
||||||
|
}
|
||||||
40
manifests/vhost.pp
Normal file
40
manifests/vhost.pp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# modules/frankenphp/manifests/vhost.pp
|
||||||
|
|
||||||
|
define frankenphp::vhost (
|
||||||
|
String $server_name,
|
||||||
|
Stdlib::Absolutepath $root_dir = '/var/www/default',
|
||||||
|
Optional[String] $proxy_target = undef,
|
||||||
|
Optional[String] $fpm_socket = undef,
|
||||||
|
Enum['php_server', 'proxy', 'fastcgi'] $mode = 'php_server',
|
||||||
|
String $extra_config = '',
|
||||||
|
Enum['present', 'absent'] $ensure = 'present',
|
||||||
|
String $order = '10',
|
||||||
|
) {
|
||||||
|
|
||||||
|
$caddyfile_path = $frankenphp::params::caddyfile_path
|
||||||
|
|
||||||
|
# Valider que les bons paramètres sont passés
|
||||||
|
# if $mode == 'php_server' and $root_dir == '/var/www/default' {
|
||||||
|
# }
|
||||||
|
if $mode == 'proxy' and !$proxy_target {
|
||||||
|
fail('Le mode proxy nécessite un $proxy_target (ex: "localhost:9001")')
|
||||||
|
}
|
||||||
|
if $mode == 'fastcgi' and !$fpm_socket {
|
||||||
|
fail('Le mode fastcgi nécessite un $fpm_socket (ex: "unix//run/php/php.sock")')
|
||||||
|
}
|
||||||
|
|
||||||
|
$vhost_content = epp('frankenphp/vhost.epp', {
|
||||||
|
server_name => $server_name,
|
||||||
|
root_dir => $root_dir,
|
||||||
|
mode => $mode,
|
||||||
|
proxy_target => $proxy_target,
|
||||||
|
fpm_socket => $fpm_socket,
|
||||||
|
extra_config => $extra_config,
|
||||||
|
})
|
||||||
|
|
||||||
|
concat::fragment { "caddyfile-vhost-${server_name}":
|
||||||
|
target => $caddyfile_path,
|
||||||
|
order => $order,
|
||||||
|
content => $vhost_content,
|
||||||
|
}
|
||||||
|
}
|
||||||
50
metadata.json
Normal file
50
metadata.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "mezcalito-frankenphp",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"author": "Léo Berry",
|
||||||
|
"summary": "Installe et configure FrankenPHP depuis un .deb et gère des applications isolées.",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"source": "https://github.com/example/puppet-frankenphp",
|
||||||
|
"project_page": "https://github.com/example/puppet-frankenphp",
|
||||||
|
"issues_url": "https://github.com/example/puppet-frankenphp/issues",
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"name": "puppetlabs/concat",
|
||||||
|
"version_requirement": ">= 6.0.0 < 10.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "puppetlabs/stdlib",
|
||||||
|
"version_requirement": ">= 6.0.0 < 10.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ajcrowe/supervisord",
|
||||||
|
"version_requirement": ">= 0.5.0 < 1.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operatingsystem_support": [
|
||||||
|
{
|
||||||
|
"operatingsystem": "Debian",
|
||||||
|
"operatingsystemrelease": [
|
||||||
|
"10",
|
||||||
|
"11",
|
||||||
|
"12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operatingsystem": "Ubuntu",
|
||||||
|
"operatingsystemrelease": [
|
||||||
|
"20.04",
|
||||||
|
"22.04"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"name": "puppet",
|
||||||
|
"version_requirement": ">= 6.0.0 < 9.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pdk-version": "3.0.0",
|
||||||
|
"template-url": "pdk-default#3.0.0",
|
||||||
|
"template-ref": "tags/3.0.0-0-g069365d"
|
||||||
|
}
|
||||||
24
templates/app_caddyfile.epp
Normal file
24
templates/app_caddyfile.epp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Caddyfile pour l'application écoutant sur localhost
|
||||||
|
{
|
||||||
|
admin off
|
||||||
|
}
|
||||||
|
|
||||||
|
:<%= $listen_port %> {
|
||||||
|
root * <%= $root_dir %>
|
||||||
|
|
||||||
|
encode zstd br gzip
|
||||||
|
|
||||||
|
# Clear Headers
|
||||||
|
header {
|
||||||
|
-Server
|
||||||
|
-X-Powered-By
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
file_server
|
||||||
|
php_server
|
||||||
|
}
|
||||||
16
templates/vhost.epp
Normal file
16
templates/vhost.epp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<%= $server_name %> {
|
||||||
|
<%- if $mode == 'php_server' { -%>
|
||||||
|
# Mode: php_server (utilisateur partagé)
|
||||||
|
root * <%= $root_dir %>
|
||||||
|
file_server
|
||||||
|
php_server
|
||||||
|
<%- } elsif $mode == 'fastcgi' { -%>
|
||||||
|
# Mode: php_fastcgi (pool FPM, utilisateur isolé)
|
||||||
|
root * <%= $root_dir %>
|
||||||
|
file_server
|
||||||
|
php_fastcgi <%= $fpm_socket %>
|
||||||
|
<%- } elsif $mode == 'proxy' { -%>
|
||||||
|
# Mode: reverse_proxy (processus FrankenPHP isolé)
|
||||||
|
reverse_proxy <%= $proxy_target %>
|
||||||
|
<%- } -%>
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user