diff --git a/src/providers/go.rs b/src/providers/go.rs index 41516afd4..28ed3ae3b 100644 --- a/src/providers/go.rs +++ b/src/providers/go.rs @@ -122,9 +122,9 @@ mod test { #[test] fn test_with_go_mod() -> Result<()> { - let go_mod_contents = r#" + let go_mod_contents = r" go 1.18 - "#; + "; assert_eq!( GolangProvider::get_nix_golang_pkg(Some(&go_mod_contents.to_string()))?, @@ -136,9 +136,9 @@ mod test { #[test] fn test_fallback_on_invalid_version() -> Result<()> { - let go_mod_contents = r#" + let go_mod_contents = r" go 1.8 - "#; + "; assert_eq!( GolangProvider::get_nix_golang_pkg(Some(&go_mod_contents.to_string()))?, diff --git a/src/providers/php/Nixpacks/Config/Template.pm b/src/providers/php/Nixpacks/Config/Template.pm deleted file mode 100644 index 3387f7ec8..000000000 --- a/src/providers/php/Nixpacks/Config/Template.pm +++ /dev/null @@ -1,52 +0,0 @@ -package Nixpacks::Config::Template; - -require 5.013002; - -use Nixpacks::Nix; - -sub if_stmt { - my ($condition, $value, $else) = @_; - - if($ENV{$condition} ne "") { - return replace_str($value); - } else { - return replace_str($else); - } -} - -sub replace_str { - my ($input) = @_; - my $new = - $input - =~ - # If statements - s{ - \$if\s*\((\w+)\)\s*\( - ([\s\S]*?) - \)\s*else\s*\( - ([\s\S]*?) - \) - }{if_stmt($1, $2, $3)}egxr - =~ - # Variables - s/\$\{(\w+)\}/$ENV{$1}/egr - =~ - # Nix paths - s/\$\!\{(\w+)\}/Nixpacks::Nix::get_nix_path($1)/egr; - return $new; -} - -sub compile_template { - my ($infile, $outfile) = @_; - open(my $handle, '<', $infile) or die "Could not open configuration file '$infile' $!"; - my $out = ''; - while (my $line = <$handle>) { - $out .= replace_str($line); - } - close(FH); - open(FH, '>', $outfile) or die "Could not write configuration file '$outfile' $!"; - print FH $out; - close(FH); -} - -1; \ No newline at end of file diff --git a/src/providers/php/Nixpacks/Nix.pm b/src/providers/php/Nixpacks/Nix.pm deleted file mode 100644 index 90b5ae9e9..000000000 --- a/src/providers/php/Nixpacks/Nix.pm +++ /dev/null @@ -1,13 +0,0 @@ -package Nixpacks::Nix; - -sub get_nix_path { - my ($exe) = @_; - - my $path = `which $exe`; - $path =~ s/\n//; - my $storePath = `nix-store -q $path`; - $storePath =~ s/\n//; - return $storePath; -} - -1; \ No newline at end of file diff --git a/src/providers/php/Nixpacks/Util/ChmodRecursive.pm b/src/providers/php/Nixpacks/Util/ChmodRecursive.pm deleted file mode 100644 index 877c62814..000000000 --- a/src/providers/php/Nixpacks/Util/ChmodRecursive.pm +++ /dev/null @@ -1,16 +0,0 @@ -# https://stackoverflow.com/a/3738367 -package Nixpacks::Util::ChmodRecursive; - -use File::Find; - -sub chmod_recursive { - my ($dir) = @_; - sub wanted - { - my $perm = -d $File::Find::name ? 0777 : 0666; - chmod $perm, $File::Find::name; - } - find(\&wanted, $dir); -} - -1; \ No newline at end of file diff --git a/src/providers/php/Nixpacks/Util/Laravel.pm b/src/providers/php/Nixpacks/Util/Laravel.pm deleted file mode 100644 index 46078c203..000000000 --- a/src/providers/php/Nixpacks/Util/Laravel.pm +++ /dev/null @@ -1,47 +0,0 @@ -package Nixpacks::Util::Laravel; - -use File::Spec::Functions qw(catfile); -use Nixpacks::Util::Logger; - -my %variable_hints = ( - APP_ENV => 'You should probably set it to `production`.', -); - -my $logger = Nixpacks::Util::Logger->new("laravel"); - -sub is_laravel { - $ENV{IS_LARAVEL} ne ""; -} - -sub check_variable { - my ($varname) = @_; - - if($ENV{$varname} eq "") { - my $hint = "Your app configuration references the $varname environment variable, but it is not set."; - if(defined $variable_hints{$varname}) { - $hint .= ' ' . $variable_hints{$varname}; - } - $logger->warn($hint); - } -} - -sub check_possible_env_errors { - my ($srcdir) = @_; - - my $config_path = catfile($srcdir, 'config', '*.php'); - my @config_files = glob($config_path); - - foreach my $config_file (@config_files) { - open(FH, '<', $config_file); - - while() { - check_variable($1) if /env\(["']([^,]*)["']\)/ and $1 ne "APP_KEY"; - } - } - - if($ENV{APP_KEY} eq "") { - $logger->warn("Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.") - } -} - -1; \ No newline at end of file diff --git a/src/providers/php/Nixpacks/Util/Logger.pm b/src/providers/php/Nixpacks/Util/Logger.pm deleted file mode 100644 index 1531deb74..000000000 --- a/src/providers/php/Nixpacks/Util/Logger.pm +++ /dev/null @@ -1,29 +0,0 @@ -package Nixpacks::Util::Logger; - -sub new { - my ($class, $tag) = @_; - my $self = bless { tag => $tag }, $class; -} - -sub log { - my ($self, $color, $message_type, $message) = @_; - my $tag = $self->{tag}; - CORE::say "\e[${color}m[$tag:$message_type]\e[0m $message"; -} - -sub info { - my ($self, $message) = @_; - $self->log(34, "info", $message); -} - -sub warn { - my ($self, $message) = @_; - $self->log(33, "warn", $message); -} - -sub err { - my ($self, $message) = @_; - $self->log(31, "error", $message); -} - -1; \ No newline at end of file diff --git a/src/providers/php/mod.rs b/src/providers/php/mod.rs index 9c24f738a..0ebde168c 100644 --- a/src/providers/php/mod.rs +++ b/src/providers/php/mod.rs @@ -69,7 +69,6 @@ impl PhpProvider { .collect::>() .join(" ") )), - Pkg::new("perl"), Pkg::new("nginx"), Pkg::new("libmysqlclient"), Pkg::new(&format!("{}Packages.composer", &php_pkg)), @@ -79,9 +78,7 @@ impl PhpProvider { .map(|extension| format!("{}Extensions.{extension}", &php_pkg)) .collect(); - if app.includes_file("package.json") { - pkgs.append(&mut NodeProvider::get_nix_packages(app, env)?); - } + pkgs.append(&mut NodeProvider::get_nix_packages(app, env)?); { let mut tmp_ext_pkgs = ext_pkgs.iter().map(|pkg| Pkg::new(pkg)).collect(); @@ -134,14 +131,14 @@ impl PhpProvider { )) } else if app.includes_file("nginx.template.conf") { StartPhase::new(format!( - "perl {} /app/nginx.template.conf /nginx.conf && (php-fpm -y {} & nginx -c /nginx.conf)", - app.asset_path("prestart.pl"), + "node {} /app/nginx.template.conf /nginx.conf && (php-fpm -y {} & nginx -c /nginx.conf)", + app.asset_path("scripts/prestart.mjs"), app.asset_path("php-fpm.conf"), )) } else { StartPhase::new(format!( - "perl {} {} /nginx.conf && (php-fpm -y {} & nginx -c /nginx.conf)", - app.asset_path("prestart.pl"), + "node {} {} /nginx.conf && (php-fpm -y {} & nginx -c /nginx.conf)", + app.asset_path("scripts/prestart.mjs"), app.asset_path("nginx.template.conf"), app.asset_path("php-fpm.conf"), )) @@ -151,13 +148,13 @@ impl PhpProvider { fn static_assets() -> StaticAssets { static_asset_list! { "nginx.template.conf" => include_str!("nginx.template.conf"), - "prestart.pl" => include_str!("prestart.pl"), + "scripts/prestart.mjs" => include_str!("scripts/prestart.mjs"), "php-fpm.conf" => include_str!("php-fpm.conf"), - "Nixpacks/Nix.pm" => include_str!("Nixpacks/Nix.pm"), - "Nixpacks/Config/Template.pm" => include_str!("Nixpacks/Config/Template.pm"), - "Nixpacks/Util/ChmodRecursive.pm" => include_str!("Nixpacks/Util/ChmodRecursive.pm"), - "Nixpacks/Util/Laravel.pm" => include_str!("Nixpacks/Util/Laravel.pm"), - "Nixpacks/Util/Logger.pm" => include_str!("Nixpacks/Util/Logger.pm") + "scripts/util/cmd.mjs" => include_str!("scripts/util/cmd.mjs"), + "scripts/util/nix.mjs" => include_str!("scripts/util/nix.mjs"), + "scripts/config/template.mjs" => include_str!("scripts/config/template.mjs"), + "scripts/util/laravel.mjs" => include_str!("scripts/util/laravel.mjs"), + "scripts/util/logger.mjs" => include_str!("scripts/util/logger.mjs") } } diff --git a/src/providers/php/prestart.pl b/src/providers/php/prestart.pl deleted file mode 100755 index 5416b4c5f..000000000 --- a/src/providers/php/prestart.pl +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env perl - -undef $/; - -use strict; -use warnings; - -use FindBin; -use lib ("$FindBin::RealBin"); - -use File::Find; -use Nixpacks::Config::Template qw(compile_template); -use Nixpacks::Util::Logger; -use Nixpacks::Util::ChmodRecursive qw(chmod_recursive); -use Nixpacks::Util::Laravel qw(is_laravel check_possible_env_errors); - -my $server_logger = Nixpacks::Util::Logger->new("server"); - -Nixpacks::Util::ChmodRecursive::chmod_recursive("/app/storage") if -e "/app/storage"; - -if ($#ARGV != 1) { - print STDERR "Usage: $0 \n"; - exit 1; -} - -if(Nixpacks::Util::Laravel::is_laravel()) { - Nixpacks::Util::Laravel::check_possible_env_errors("/app"); -} - -Nixpacks::Config::Template::compile_template($ARGV[0], $ARGV[1]); -my $port = $ENV{"PORT"}; -$server_logger->info("Server starting on port $port"); \ No newline at end of file diff --git a/src/providers/php/scripts/config/template.mjs b/src/providers/php/scripts/config/template.mjs new file mode 100644 index 000000000..1c1229915 --- /dev/null +++ b/src/providers/php/scripts/config/template.mjs @@ -0,0 +1,24 @@ +import { readFile, writeFile } from "fs/promises"; +import { getNixPath } from "../util/nix.mjs"; + +const replaceStr = input => + input + // If statements + .replaceAll(/\$if\s*\((\w+)\)\s*\(([^]*?)\)\s*else\s*\(([^]*?)\)/gm, + (_all, condition, value, otherwise) => + process.env[condition] ? replaceStr(value) : replaceStr(otherwise) + ) + // Variables + .replaceAll(/\${(\w+)}/g, + (_all, name) => process.env[name] + ) + // Nix paths + .replaceAll(/\$!{(\w+)}/g, + (_all, exe) => getNixPath(exe) + ) + +export async function compileTemplate(infile, outfile) { + await writeFile(outfile, + replaceStr(await readFile(infile, { encoding: 'utf8' })), + { encoding: 'utf8' }) +} diff --git a/src/providers/php/scripts/prestart.mjs b/src/providers/php/scripts/prestart.mjs new file mode 100755 index 000000000..907998277 --- /dev/null +++ b/src/providers/php/scripts/prestart.mjs @@ -0,0 +1,27 @@ +#!/usr/bin/env node +import { compileTemplate } from "./config/template.mjs"; +import { e } from "./util/cmd.mjs"; +import { checkEnvErrors, isLaravel } from "./util/laravel.mjs"; +import Logger from "./util/logger.mjs"; +import { access, constants } from 'node:fs/promises' + +const prestartLogger = new Logger('prestart'); +const serverLogger = new Logger('server'); + +if (process.argv.length != 4) { + prestartLogger.error(`Usage: ${process.argv[1]} `) + process.exit(1); +} + +if (isLaravel()) { + checkEnvErrors('/app') +} + +await Promise.all([ + access('/app/storage', constants.R_OK) + .then(() => e('chmod -R ugo+rw /app/storage')) + .catch(() => {}), + compileTemplate(process.argv[2], process.argv[3]) +]).catch(err => prestartLogger.error(err)); + +serverLogger.info(`Server starting on port ${process.env.PORT}`) diff --git a/src/providers/php/scripts/util/cmd.mjs b/src/providers/php/scripts/util/cmd.mjs new file mode 100644 index 000000000..e079dca0c --- /dev/null +++ b/src/providers/php/scripts/util/cmd.mjs @@ -0,0 +1,3 @@ +import { execSync } from "child_process"; + +export const e = cmd => execSync(cmd).toString().replace('\n', ''); \ No newline at end of file diff --git a/src/providers/php/scripts/util/laravel.mjs b/src/providers/php/scripts/util/laravel.mjs new file mode 100644 index 000000000..e5806fcc6 --- /dev/null +++ b/src/providers/php/scripts/util/laravel.mjs @@ -0,0 +1,41 @@ +import Logger from "./logger.mjs" +import * as fs from 'node:fs/promises' +import * as path from 'node:path' + +const variableHints = { + 'APP_ENV': 'You should probably set this to `production`.' +}; + +const logger = new Logger('laravel'); + +export const isLaravel = () => process.env['IS_LARAVEL'] != null; + +function checkVariable(name) { + if (!process.env[name]) { + let hint = + `Your app configuration references the ${name} environment variable, but it is not set.` + + (variableHints[name] ?? ''); + + logger.warn(hint); + } +} + +export async function checkEnvErrors(srcdir) { + const envRegex = /env\(["']([^,]*)["']\)/g; + const configDir = path.join(srcdir, 'config'); + + const config = + (await Promise.all( + (await fs.readdir(configDir)) + .filter(fileName => fileName.endsWith('.php')) + .map(fileName => fs.readFile(path.join(configDir, fileName))) + )).join(''); + + for (const match of config.matchAll(envRegex)) { + if (match[1] != 'APP_KEY') checkVariable(match[1]); + } + + if (!process.env.APP_KEY) { + logger.warn('Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.'); + } +} diff --git a/src/providers/php/scripts/util/logger.mjs b/src/providers/php/scripts/util/logger.mjs new file mode 100644 index 000000000..83db677ac --- /dev/null +++ b/src/providers/php/scripts/util/logger.mjs @@ -0,0 +1,27 @@ +export default class Logger { + /** @type string */ + #tag; + + /** + * @param {string} tag + */ + constructor(tag) { + this.#tag = tag + } + + #log(color, messageType, message, fn = console.log) { + fn(`\x1b[${color}m[${this.#tag}:${messageType}]\x1b[0m ${message}`) + } + + info(message) { + this.#log(34, 'info', message) + } + + warn(message) { + this.#log(35, 'warn', message, console.warn) + } + + error(message) { + this.#log(31, 'error', message, console.error) + } +} diff --git a/src/providers/php/scripts/util/nix.mjs b/src/providers/php/scripts/util/nix.mjs new file mode 100644 index 000000000..d86ef1e68 --- /dev/null +++ b/src/providers/php/scripts/util/nix.mjs @@ -0,0 +1,3 @@ +import { e } from "./cmd.mjs"; + +export const getNixPath = (exe) => e(`nix-store -q ${e(`which ${exe}`)}`); diff --git a/tests/snapshots/generate_plan_tests__php_api.snap b/tests/snapshots/generate_plan_tests__php_api.snap index 06100f601..a464b354f 100644 --- a/tests/snapshots/generate_plan_tests__php_api.snap +++ b/tests/snapshots/generate_plan_tests__php_api.snap @@ -12,14 +12,14 @@ expression: plan "PORT": "80" }, "staticAssets": { - "Nixpacks/Config/Template.pm": "package Nixpacks::Config::Template;\n\nrequire 5.013002;\n\nuse Nixpacks::Nix;\n\nsub if_stmt {\n my ($condition, $value, $else) = @_;\n\n if($ENV{$condition} ne \"\") {\n return replace_str($value);\n } else {\n return replace_str($else);\n }\n}\n\nsub replace_str {\n my ($input) = @_;\n my $new = \n $input \n =~ \n # If statements\n s{\n \\$if\\s*\\((\\w+)\\)\\s*\\(\n ([\\s\\S]*?)\n \\)\\s*else\\s*\\(\n ([\\s\\S]*?)\n \\)\n }{if_stmt($1, $2, $3)}egxr\n =~\n # Variables\n s/\\$\\{(\\w+)\\}/$ENV{$1}/egr\n =~ \n # Nix paths\n s/\\$\\!\\{(\\w+)\\}/Nixpacks::Nix::get_nix_path($1)/egr;\n return $new;\n}\n\nsub compile_template {\n my ($infile, $outfile) = @_;\n open(my $handle, '<', $infile) or die \"Could not open configuration file '$infile' $!\";\n my $out = '';\n while (my $line = <$handle>) {\n $out .= replace_str($line);\n }\n close(FH);\n open(FH, '>', $outfile) or die \"Could not write configuration file '$outfile' $!\";\n print FH $out;\n close(FH);\n}\n\n1;", - "Nixpacks/Nix.pm": "package Nixpacks::Nix;\n\nsub get_nix_path {\n my ($exe) = @_;\n \n my $path = `which $exe`;\n $path =~ s/\\n//;\n my $storePath = `nix-store -q $path`;\n $storePath =~ s/\\n//;\n return $storePath;\n}\n\n1;", - "Nixpacks/Util/ChmodRecursive.pm": "# https://stackoverflow.com/a/3738367\npackage Nixpacks::Util::ChmodRecursive;\n\nuse File::Find;\n\nsub chmod_recursive {\n my ($dir) = @_;\n sub wanted\n {\n my $perm = -d $File::Find::name ? 0777 : 0666;\n chmod $perm, $File::Find::name;\n }\n find(\\&wanted, $dir);\n}\n\n1;", - "Nixpacks/Util/Laravel.pm": "package Nixpacks::Util::Laravel;\n\nuse File::Spec::Functions qw(catfile);\nuse Nixpacks::Util::Logger;\n\nmy %variable_hints = (\n APP_ENV => 'You should probably set it to `production`.',\n);\n\nmy $logger = Nixpacks::Util::Logger->new(\"laravel\");\n\nsub is_laravel {\n $ENV{IS_LARAVEL} ne \"\";\n}\n\nsub check_variable {\n my ($varname) = @_;\n\n if($ENV{$varname} eq \"\") {\n my $hint = \"Your app configuration references the $varname environment variable, but it is not set.\";\n if(defined $variable_hints{$varname}) {\n $hint .= ' ' . $variable_hints{$varname};\n }\n $logger->warn($hint);\n }\n}\n\nsub check_possible_env_errors {\n my ($srcdir) = @_;\n\n my $config_path = catfile($srcdir, 'config', '*.php');\n my @config_files = glob($config_path);\n\n foreach my $config_file (@config_files) {\n open(FH, '<', $config_file);\n\n while() {\n check_variable($1) if /env\\([\"']([^,]*)[\"']\\)/ and $1 ne \"APP_KEY\";\n }\n }\n\t\n\tif($ENV{APP_KEY} eq \"\") {\n\t\t$logger->warn(\"Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.\")\n\t}\n}\n\n1;", - "Nixpacks/Util/Logger.pm": "package Nixpacks::Util::Logger;\n\nsub new {\n my ($class, $tag) = @_;\n my $self = bless { tag => $tag }, $class;\n}\n\nsub log {\n my ($self, $color, $message_type, $message) = @_;\n my $tag = $self->{tag};\n CORE::say \"\\e[${color}m[$tag:$message_type]\\e[0m $message\";\n}\n\nsub info {\n my ($self, $message) = @_;\n $self->log(34, \"info\", $message);\n}\n\nsub warn {\n my ($self, $message) = @_;\n $self->log(33, \"warn\", $message);\n}\n\nsub err {\n my ($self, $message) = @_;\n $self->log(31, \"error\", $message);\n}\n\n1;", "nginx.template.conf": "worker_processes 5;\ndaemon off;\n\nworker_rlimit_nofile 8192;\n\nevents {\n worker_connections 4096; # Default: 1024\n}\n\nhttp {\n include $!{nginx}/conf/mime.types;\n index index.html index.htm index.php;\n\n default_type application/octet-stream;\n log_format main '$remote_addr - $remote_user [$time_local] $status '\n '\"$request\" $body_bytes_sent \"$http_referer\" '\n '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n access_log /dev/stdout;\n error_log /dev/stdout;\n sendfile on;\n tcp_nopush on;\n server_names_hash_bucket_size 128; # this seems to be required for some vhosts\n\n server {\n listen ${PORT};\n listen [::]:${PORT};\n server_name localhost;\n\n $if(NIXPACKS_PHP_ROOT_DIR) (\n root ${NIXPACKS_PHP_ROOT_DIR};\n ) else (\n root /app;\n )\n \n add_header X-Frame-Options \"SAMEORIGIN\";\n add_header X-Content-Type-Options \"nosniff\";\n \n index index.php;\n \n charset utf-8;\n \n $if(IS_LARAVEL) (\n location / {\n try_files $uri $uri/ /index.php?$query_string;\n }\n ) else ()\n \n $if(NIXPACKS_PHP_FALLBACK_PATH) (\n location / {\n try_files $uri $uri/ ${NIXPACKS_PHP_FALLBACK_PATH}?$query_string;\n }\n ) else ()\n \n location = /favicon.ico { access_log off; log_not_found off; }\n location = /robots.txt { access_log off; log_not_found off; }\n \n $if(IS_LARAVEL) (\n error_page 404 /index.php;\n ) else ()\n \n location ~ \\.php$ {\n fastcgi_pass 127.0.0.1:9000;\n fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n include $!{nginx}/conf/fastcgi_params;\n include $!{nginx}/conf/fastcgi.conf;\n }\n \n location ~ /\\.(?!well-known).* {\n deny all;\n }\n }\n}", "php-fpm.conf": "[www]\nlisten = 127.0.0.1:9000\nuser = nobody\npm = dynamic\npm.max_children = 50\npm.min_spare_servers = 4\npm.max_spare_servers = 32\npm.start_servers = 18\nclear_env = no\n", - "prestart.pl": "#!/usr/bin/env perl\n\nundef $/;\n\nuse strict;\nuse warnings;\n\nuse FindBin;\nuse lib (\"$FindBin::RealBin\");\n\nuse File::Find;\nuse Nixpacks::Config::Template qw(compile_template);\nuse Nixpacks::Util::Logger;\nuse Nixpacks::Util::ChmodRecursive qw(chmod_recursive);\nuse Nixpacks::Util::Laravel qw(is_laravel check_possible_env_errors);\n\nmy $server_logger = Nixpacks::Util::Logger->new(\"server\");\n\nNixpacks::Util::ChmodRecursive::chmod_recursive(\"/app/storage\") if -e \"/app/storage\";\n\nif ($#ARGV != 1) {\n print STDERR \"Usage: $0 \\n\";\n exit 1;\n}\n\nif(Nixpacks::Util::Laravel::is_laravel()) {\n Nixpacks::Util::Laravel::check_possible_env_errors(\"/app\");\n}\n\nNixpacks::Config::Template::compile_template($ARGV[0], $ARGV[1]);\nmy $port = $ENV{\"PORT\"};\n$server_logger->info(\"Server starting on port $port\");" + "scripts/config/template.mjs": "import { readFile, writeFile } from \"fs/promises\";\nimport { getNixPath } from \"../util/nix.mjs\";\n\nconst replaceStr = input =>\n input\n // If statements\n .replaceAll(/\\$if\\s*\\((\\w+)\\)\\s*\\(([^]*?)\\)\\s*else\\s*\\(([^]*?)\\)/gm,\n (_all, condition, value, otherwise) =>\n process.env[condition] ? replaceStr(value) : replaceStr(otherwise)\n )\n // Variables\n .replaceAll(/\\${(\\w+)}/g,\n (_all, name) => process.env[name]\n )\n // Nix paths\n .replaceAll(/\\$!{(\\w+)}/g,\n (_all, exe) => getNixPath(exe)\n )\n\nexport async function compileTemplate(infile, outfile) {\n await writeFile(outfile,\n replaceStr(await readFile(infile, { encoding: 'utf8' })),\n { encoding: 'utf8' })\n}\n", + "scripts/prestart.mjs": "#!/usr/bin/env node\nimport { compileTemplate } from \"./config/template.mjs\";\nimport { e } from \"./util/cmd.mjs\";\nimport { checkEnvErrors, isLaravel } from \"./util/laravel.mjs\";\nimport Logger from \"./util/logger.mjs\";\nimport { access, constants } from 'node:fs/promises'\n\nconst prestartLogger = new Logger('prestart');\nconst serverLogger = new Logger('server');\n\nif (process.argv.length != 4) {\n prestartLogger.error(`Usage: ${process.argv[1]} `)\n process.exit(1);\n}\n\nif (isLaravel()) {\n checkEnvErrors('/app')\n}\n\nawait Promise.all([\n access('/app/storage', constants.R_OK)\n .then(() => e('chmod -R ugo+rw /app/storage'))\n .catch(() => {}),\n compileTemplate(process.argv[2], process.argv[3])\n]).catch(err => prestartLogger.error(err));\n\nserverLogger.info(`Server starting on port ${process.env.PORT}`)\n", + "scripts/util/cmd.mjs": "import { execSync } from \"child_process\";\n\nexport const e = cmd => execSync(cmd).toString().replace('\\n', '');", + "scripts/util/laravel.mjs": "import Logger from \"./logger.mjs\"\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\n\nconst variableHints = {\n 'APP_ENV': 'You should probably set this to `production`.'\n};\n\nconst logger = new Logger('laravel');\n\nexport const isLaravel = () => process.env['IS_LARAVEL'] != null;\n\nfunction checkVariable(name) {\n if (!process.env[name]) {\n let hint =\n `Your app configuration references the ${name} environment variable, but it is not set.`\n + (variableHints[name] ?? '');\n\n logger.warn(hint);\n }\n}\n\nexport async function checkEnvErrors(srcdir) {\n const envRegex = /env\\([\"']([^,]*)[\"']\\)/g;\n const configDir = path.join(srcdir, 'config');\n\n const config =\n (await Promise.all(\n (await fs.readdir(configDir))\n .filter(fileName => fileName.endsWith('.php'))\n .map(fileName => fs.readFile(path.join(configDir, fileName)))\n )).join('');\n\n for (const match of config.matchAll(envRegex)) {\n if (match[1] != 'APP_KEY') checkVariable(match[1]);\n }\n\n if (!process.env.APP_KEY) {\n logger.warn('Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.');\n }\n}\n", + "scripts/util/logger.mjs": "export default class Logger {\n /** @type string */\n #tag;\n\n /**\n * @param {string} tag\n */\n constructor(tag) {\n this.#tag = tag\n }\n\n #log(color, messageType, message, fn = console.log) {\n fn(`\\x1b[${color}m[${this.#tag}:${messageType}]\\x1b[0m ${message}`)\n }\n\n info(message) {\n this.#log(34, 'info', message)\n }\n\n warn(message) {\n this.#log(35, 'warn', message, console.warn)\n }\n\n error(message) {\n this.#log(31, 'error', message, console.error)\n }\n}\n", + "scripts/util/nix.mjs": "import { e } from \"./cmd.mjs\";\n\nexport const getNixPath = (exe) => e(`nix-store -q ${e(`which ${exe}`)}`);\n" }, "phases": { "install": { @@ -36,19 +36,22 @@ expression: plan "name": "setup", "nixPkgs": [ "(php82.withExtensions (pe: pe.enabled ++ []))", - "perl", "nginx", "libmysqlclient", - "php82Packages.composer" + "php82Packages.composer", + "nodejs_18", + "npm-9_x" ], "nixLibs": [ "libmysqlclient" ], - "nixOverlays": [], + "nixOverlays": [ + "https://github.com/railwayapp/nix-npm-overlay/archive/main.tar.gz" + ], "nixpkgsArchive": "[archive]" } }, "start": { - "cmd": "perl /assets/prestart.pl /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" + "cmd": "node /assets/scripts/prestart.mjs /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" } } diff --git a/tests/snapshots/generate_plan_tests__php_custom_config.snap b/tests/snapshots/generate_plan_tests__php_custom_config.snap index addd950a3..e29909a15 100644 --- a/tests/snapshots/generate_plan_tests__php_custom_config.snap +++ b/tests/snapshots/generate_plan_tests__php_custom_config.snap @@ -10,14 +10,14 @@ expression: plan "PORT": "80" }, "staticAssets": { - "Nixpacks/Config/Template.pm": "package Nixpacks::Config::Template;\n\nrequire 5.013002;\n\nuse Nixpacks::Nix;\n\nsub if_stmt {\n my ($condition, $value, $else) = @_;\n\n if($ENV{$condition} ne \"\") {\n return replace_str($value);\n } else {\n return replace_str($else);\n }\n}\n\nsub replace_str {\n my ($input) = @_;\n my $new = \n $input \n =~ \n # If statements\n s{\n \\$if\\s*\\((\\w+)\\)\\s*\\(\n ([\\s\\S]*?)\n \\)\\s*else\\s*\\(\n ([\\s\\S]*?)\n \\)\n }{if_stmt($1, $2, $3)}egxr\n =~\n # Variables\n s/\\$\\{(\\w+)\\}/$ENV{$1}/egr\n =~ \n # Nix paths\n s/\\$\\!\\{(\\w+)\\}/Nixpacks::Nix::get_nix_path($1)/egr;\n return $new;\n}\n\nsub compile_template {\n my ($infile, $outfile) = @_;\n open(my $handle, '<', $infile) or die \"Could not open configuration file '$infile' $!\";\n my $out = '';\n while (my $line = <$handle>) {\n $out .= replace_str($line);\n }\n close(FH);\n open(FH, '>', $outfile) or die \"Could not write configuration file '$outfile' $!\";\n print FH $out;\n close(FH);\n}\n\n1;", - "Nixpacks/Nix.pm": "package Nixpacks::Nix;\n\nsub get_nix_path {\n my ($exe) = @_;\n \n my $path = `which $exe`;\n $path =~ s/\\n//;\n my $storePath = `nix-store -q $path`;\n $storePath =~ s/\\n//;\n return $storePath;\n}\n\n1;", - "Nixpacks/Util/ChmodRecursive.pm": "# https://stackoverflow.com/a/3738367\npackage Nixpacks::Util::ChmodRecursive;\n\nuse File::Find;\n\nsub chmod_recursive {\n my ($dir) = @_;\n sub wanted\n {\n my $perm = -d $File::Find::name ? 0777 : 0666;\n chmod $perm, $File::Find::name;\n }\n find(\\&wanted, $dir);\n}\n\n1;", - "Nixpacks/Util/Laravel.pm": "package Nixpacks::Util::Laravel;\n\nuse File::Spec::Functions qw(catfile);\nuse Nixpacks::Util::Logger;\n\nmy %variable_hints = (\n APP_ENV => 'You should probably set it to `production`.',\n);\n\nmy $logger = Nixpacks::Util::Logger->new(\"laravel\");\n\nsub is_laravel {\n $ENV{IS_LARAVEL} ne \"\";\n}\n\nsub check_variable {\n my ($varname) = @_;\n\n if($ENV{$varname} eq \"\") {\n my $hint = \"Your app configuration references the $varname environment variable, but it is not set.\";\n if(defined $variable_hints{$varname}) {\n $hint .= ' ' . $variable_hints{$varname};\n }\n $logger->warn($hint);\n }\n}\n\nsub check_possible_env_errors {\n my ($srcdir) = @_;\n\n my $config_path = catfile($srcdir, 'config', '*.php');\n my @config_files = glob($config_path);\n\n foreach my $config_file (@config_files) {\n open(FH, '<', $config_file);\n\n while() {\n check_variable($1) if /env\\([\"']([^,]*)[\"']\\)/ and $1 ne \"APP_KEY\";\n }\n }\n\t\n\tif($ENV{APP_KEY} eq \"\") {\n\t\t$logger->warn(\"Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.\")\n\t}\n}\n\n1;", - "Nixpacks/Util/Logger.pm": "package Nixpacks::Util::Logger;\n\nsub new {\n my ($class, $tag) = @_;\n my $self = bless { tag => $tag }, $class;\n}\n\nsub log {\n my ($self, $color, $message_type, $message) = @_;\n my $tag = $self->{tag};\n CORE::say \"\\e[${color}m[$tag:$message_type]\\e[0m $message\";\n}\n\nsub info {\n my ($self, $message) = @_;\n $self->log(34, \"info\", $message);\n}\n\nsub warn {\n my ($self, $message) = @_;\n $self->log(33, \"warn\", $message);\n}\n\nsub err {\n my ($self, $message) = @_;\n $self->log(31, \"error\", $message);\n}\n\n1;", "nginx.template.conf": "worker_processes 5;\ndaemon off;\n\nworker_rlimit_nofile 8192;\n\nevents {\n worker_connections 4096; # Default: 1024\n}\n\nhttp {\n include $!{nginx}/conf/mime.types;\n index index.html index.htm index.php;\n\n default_type application/octet-stream;\n log_format main '$remote_addr - $remote_user [$time_local] $status '\n '\"$request\" $body_bytes_sent \"$http_referer\" '\n '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n access_log /dev/stdout;\n error_log /dev/stdout;\n sendfile on;\n tcp_nopush on;\n server_names_hash_bucket_size 128; # this seems to be required for some vhosts\n\n server {\n listen ${PORT};\n listen [::]:${PORT};\n server_name localhost;\n\n $if(NIXPACKS_PHP_ROOT_DIR) (\n root ${NIXPACKS_PHP_ROOT_DIR};\n ) else (\n root /app;\n )\n \n add_header X-Frame-Options \"SAMEORIGIN\";\n add_header X-Content-Type-Options \"nosniff\";\n \n index index.php;\n \n charset utf-8;\n \n $if(IS_LARAVEL) (\n location / {\n try_files $uri $uri/ /index.php?$query_string;\n }\n ) else ()\n \n $if(NIXPACKS_PHP_FALLBACK_PATH) (\n location / {\n try_files $uri $uri/ ${NIXPACKS_PHP_FALLBACK_PATH}?$query_string;\n }\n ) else ()\n \n location = /favicon.ico { access_log off; log_not_found off; }\n location = /robots.txt { access_log off; log_not_found off; }\n \n $if(IS_LARAVEL) (\n error_page 404 /index.php;\n ) else ()\n \n location ~ \\.php$ {\n fastcgi_pass 127.0.0.1:9000;\n fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n include $!{nginx}/conf/fastcgi_params;\n include $!{nginx}/conf/fastcgi.conf;\n }\n \n location ~ /\\.(?!well-known).* {\n deny all;\n }\n }\n}", "php-fpm.conf": "[www]\nlisten = 127.0.0.1:9000\nuser = nobody\npm = dynamic\npm.max_children = 50\npm.min_spare_servers = 4\npm.max_spare_servers = 32\npm.start_servers = 18\nclear_env = no\n", - "prestart.pl": "#!/usr/bin/env perl\n\nundef $/;\n\nuse strict;\nuse warnings;\n\nuse FindBin;\nuse lib (\"$FindBin::RealBin\");\n\nuse File::Find;\nuse Nixpacks::Config::Template qw(compile_template);\nuse Nixpacks::Util::Logger;\nuse Nixpacks::Util::ChmodRecursive qw(chmod_recursive);\nuse Nixpacks::Util::Laravel qw(is_laravel check_possible_env_errors);\n\nmy $server_logger = Nixpacks::Util::Logger->new(\"server\");\n\nNixpacks::Util::ChmodRecursive::chmod_recursive(\"/app/storage\") if -e \"/app/storage\";\n\nif ($#ARGV != 1) {\n print STDERR \"Usage: $0 \\n\";\n exit 1;\n}\n\nif(Nixpacks::Util::Laravel::is_laravel()) {\n Nixpacks::Util::Laravel::check_possible_env_errors(\"/app\");\n}\n\nNixpacks::Config::Template::compile_template($ARGV[0], $ARGV[1]);\nmy $port = $ENV{\"PORT\"};\n$server_logger->info(\"Server starting on port $port\");" + "scripts/config/template.mjs": "import { readFile, writeFile } from \"fs/promises\";\nimport { getNixPath } from \"../util/nix.mjs\";\n\nconst replaceStr = input =>\n input\n // If statements\n .replaceAll(/\\$if\\s*\\((\\w+)\\)\\s*\\(([^]*?)\\)\\s*else\\s*\\(([^]*?)\\)/gm,\n (_all, condition, value, otherwise) =>\n process.env[condition] ? replaceStr(value) : replaceStr(otherwise)\n )\n // Variables\n .replaceAll(/\\${(\\w+)}/g,\n (_all, name) => process.env[name]\n )\n // Nix paths\n .replaceAll(/\\$!{(\\w+)}/g,\n (_all, exe) => getNixPath(exe)\n )\n\nexport async function compileTemplate(infile, outfile) {\n await writeFile(outfile,\n replaceStr(await readFile(infile, { encoding: 'utf8' })),\n { encoding: 'utf8' })\n}\n", + "scripts/prestart.mjs": "#!/usr/bin/env node\nimport { compileTemplate } from \"./config/template.mjs\";\nimport { e } from \"./util/cmd.mjs\";\nimport { checkEnvErrors, isLaravel } from \"./util/laravel.mjs\";\nimport Logger from \"./util/logger.mjs\";\nimport { access, constants } from 'node:fs/promises'\n\nconst prestartLogger = new Logger('prestart');\nconst serverLogger = new Logger('server');\n\nif (process.argv.length != 4) {\n prestartLogger.error(`Usage: ${process.argv[1]} `)\n process.exit(1);\n}\n\nif (isLaravel()) {\n checkEnvErrors('/app')\n}\n\nawait Promise.all([\n access('/app/storage', constants.R_OK)\n .then(() => e('chmod -R ugo+rw /app/storage'))\n .catch(() => {}),\n compileTemplate(process.argv[2], process.argv[3])\n]).catch(err => prestartLogger.error(err));\n\nserverLogger.info(`Server starting on port ${process.env.PORT}`)\n", + "scripts/util/cmd.mjs": "import { execSync } from \"child_process\";\n\nexport const e = cmd => execSync(cmd).toString().replace('\\n', '');", + "scripts/util/laravel.mjs": "import Logger from \"./logger.mjs\"\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\n\nconst variableHints = {\n 'APP_ENV': 'You should probably set this to `production`.'\n};\n\nconst logger = new Logger('laravel');\n\nexport const isLaravel = () => process.env['IS_LARAVEL'] != null;\n\nfunction checkVariable(name) {\n if (!process.env[name]) {\n let hint =\n `Your app configuration references the ${name} environment variable, but it is not set.`\n + (variableHints[name] ?? '');\n\n logger.warn(hint);\n }\n}\n\nexport async function checkEnvErrors(srcdir) {\n const envRegex = /env\\([\"']([^,]*)[\"']\\)/g;\n const configDir = path.join(srcdir, 'config');\n\n const config =\n (await Promise.all(\n (await fs.readdir(configDir))\n .filter(fileName => fileName.endsWith('.php'))\n .map(fileName => fs.readFile(path.join(configDir, fileName)))\n )).join('');\n\n for (const match of config.matchAll(envRegex)) {\n if (match[1] != 'APP_KEY') checkVariable(match[1]);\n }\n\n if (!process.env.APP_KEY) {\n logger.warn('Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.');\n }\n}\n", + "scripts/util/logger.mjs": "export default class Logger {\n /** @type string */\n #tag;\n\n /**\n * @param {string} tag\n */\n constructor(tag) {\n this.#tag = tag\n }\n\n #log(color, messageType, message, fn = console.log) {\n fn(`\\x1b[${color}m[${this.#tag}:${messageType}]\\x1b[0m ${message}`)\n }\n\n info(message) {\n this.#log(34, 'info', message)\n }\n\n warn(message) {\n this.#log(35, 'warn', message, console.warn)\n }\n\n error(message) {\n this.#log(31, 'error', message, console.error)\n }\n}\n", + "scripts/util/nix.mjs": "import { e } from \"./cmd.mjs\";\n\nexport const getNixPath = (exe) => e(`nix-store -q ${e(`which ${exe}`)}`);\n" }, "phases": { "install": { @@ -33,19 +33,22 @@ expression: plan "name": "setup", "nixPkgs": [ "(php.withExtensions (pe: pe.enabled ++ []))", - "perl", "nginx", "libmysqlclient", - "phpPackages.composer" + "phpPackages.composer", + "nodejs_18", + "npm-9_x" ], "nixLibs": [ "libmysqlclient" ], - "nixOverlays": [], + "nixOverlays": [ + "https://github.com/railwayapp/nix-npm-overlay/archive/main.tar.gz" + ], "nixpkgsArchive": "[archive]" } }, "start": { - "cmd": "perl /assets/prestart.pl /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" + "cmd": "node /assets/scripts/prestart.mjs /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" } } diff --git a/tests/snapshots/generate_plan_tests__php_laravel.snap b/tests/snapshots/generate_plan_tests__php_laravel.snap index 91c0b8752..c3f96d6db 100644 --- a/tests/snapshots/generate_plan_tests__php_laravel.snap +++ b/tests/snapshots/generate_plan_tests__php_laravel.snap @@ -12,14 +12,14 @@ expression: plan "PORT": "80" }, "staticAssets": { - "Nixpacks/Config/Template.pm": "package Nixpacks::Config::Template;\n\nrequire 5.013002;\n\nuse Nixpacks::Nix;\n\nsub if_stmt {\n my ($condition, $value, $else) = @_;\n\n if($ENV{$condition} ne \"\") {\n return replace_str($value);\n } else {\n return replace_str($else);\n }\n}\n\nsub replace_str {\n my ($input) = @_;\n my $new = \n $input \n =~ \n # If statements\n s{\n \\$if\\s*\\((\\w+)\\)\\s*\\(\n ([\\s\\S]*?)\n \\)\\s*else\\s*\\(\n ([\\s\\S]*?)\n \\)\n }{if_stmt($1, $2, $3)}egxr\n =~\n # Variables\n s/\\$\\{(\\w+)\\}/$ENV{$1}/egr\n =~ \n # Nix paths\n s/\\$\\!\\{(\\w+)\\}/Nixpacks::Nix::get_nix_path($1)/egr;\n return $new;\n}\n\nsub compile_template {\n my ($infile, $outfile) = @_;\n open(my $handle, '<', $infile) or die \"Could not open configuration file '$infile' $!\";\n my $out = '';\n while (my $line = <$handle>) {\n $out .= replace_str($line);\n }\n close(FH);\n open(FH, '>', $outfile) or die \"Could not write configuration file '$outfile' $!\";\n print FH $out;\n close(FH);\n}\n\n1;", - "Nixpacks/Nix.pm": "package Nixpacks::Nix;\n\nsub get_nix_path {\n my ($exe) = @_;\n \n my $path = `which $exe`;\n $path =~ s/\\n//;\n my $storePath = `nix-store -q $path`;\n $storePath =~ s/\\n//;\n return $storePath;\n}\n\n1;", - "Nixpacks/Util/ChmodRecursive.pm": "# https://stackoverflow.com/a/3738367\npackage Nixpacks::Util::ChmodRecursive;\n\nuse File::Find;\n\nsub chmod_recursive {\n my ($dir) = @_;\n sub wanted\n {\n my $perm = -d $File::Find::name ? 0777 : 0666;\n chmod $perm, $File::Find::name;\n }\n find(\\&wanted, $dir);\n}\n\n1;", - "Nixpacks/Util/Laravel.pm": "package Nixpacks::Util::Laravel;\n\nuse File::Spec::Functions qw(catfile);\nuse Nixpacks::Util::Logger;\n\nmy %variable_hints = (\n APP_ENV => 'You should probably set it to `production`.',\n);\n\nmy $logger = Nixpacks::Util::Logger->new(\"laravel\");\n\nsub is_laravel {\n $ENV{IS_LARAVEL} ne \"\";\n}\n\nsub check_variable {\n my ($varname) = @_;\n\n if($ENV{$varname} eq \"\") {\n my $hint = \"Your app configuration references the $varname environment variable, but it is not set.\";\n if(defined $variable_hints{$varname}) {\n $hint .= ' ' . $variable_hints{$varname};\n }\n $logger->warn($hint);\n }\n}\n\nsub check_possible_env_errors {\n my ($srcdir) = @_;\n\n my $config_path = catfile($srcdir, 'config', '*.php');\n my @config_files = glob($config_path);\n\n foreach my $config_file (@config_files) {\n open(FH, '<', $config_file);\n\n while() {\n check_variable($1) if /env\\([\"']([^,]*)[\"']\\)/ and $1 ne \"APP_KEY\";\n }\n }\n\t\n\tif($ENV{APP_KEY} eq \"\") {\n\t\t$logger->warn(\"Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.\")\n\t}\n}\n\n1;", - "Nixpacks/Util/Logger.pm": "package Nixpacks::Util::Logger;\n\nsub new {\n my ($class, $tag) = @_;\n my $self = bless { tag => $tag }, $class;\n}\n\nsub log {\n my ($self, $color, $message_type, $message) = @_;\n my $tag = $self->{tag};\n CORE::say \"\\e[${color}m[$tag:$message_type]\\e[0m $message\";\n}\n\nsub info {\n my ($self, $message) = @_;\n $self->log(34, \"info\", $message);\n}\n\nsub warn {\n my ($self, $message) = @_;\n $self->log(33, \"warn\", $message);\n}\n\nsub err {\n my ($self, $message) = @_;\n $self->log(31, \"error\", $message);\n}\n\n1;", "nginx.template.conf": "worker_processes 5;\ndaemon off;\n\nworker_rlimit_nofile 8192;\n\nevents {\n worker_connections 4096; # Default: 1024\n}\n\nhttp {\n include $!{nginx}/conf/mime.types;\n index index.html index.htm index.php;\n\n default_type application/octet-stream;\n log_format main '$remote_addr - $remote_user [$time_local] $status '\n '\"$request\" $body_bytes_sent \"$http_referer\" '\n '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n access_log /dev/stdout;\n error_log /dev/stdout;\n sendfile on;\n tcp_nopush on;\n server_names_hash_bucket_size 128; # this seems to be required for some vhosts\n\n server {\n listen ${PORT};\n listen [::]:${PORT};\n server_name localhost;\n\n $if(NIXPACKS_PHP_ROOT_DIR) (\n root ${NIXPACKS_PHP_ROOT_DIR};\n ) else (\n root /app;\n )\n \n add_header X-Frame-Options \"SAMEORIGIN\";\n add_header X-Content-Type-Options \"nosniff\";\n \n index index.php;\n \n charset utf-8;\n \n $if(IS_LARAVEL) (\n location / {\n try_files $uri $uri/ /index.php?$query_string;\n }\n ) else ()\n \n $if(NIXPACKS_PHP_FALLBACK_PATH) (\n location / {\n try_files $uri $uri/ ${NIXPACKS_PHP_FALLBACK_PATH}?$query_string;\n }\n ) else ()\n \n location = /favicon.ico { access_log off; log_not_found off; }\n location = /robots.txt { access_log off; log_not_found off; }\n \n $if(IS_LARAVEL) (\n error_page 404 /index.php;\n ) else ()\n \n location ~ \\.php$ {\n fastcgi_pass 127.0.0.1:9000;\n fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n include $!{nginx}/conf/fastcgi_params;\n include $!{nginx}/conf/fastcgi.conf;\n }\n \n location ~ /\\.(?!well-known).* {\n deny all;\n }\n }\n}", "php-fpm.conf": "[www]\nlisten = 127.0.0.1:9000\nuser = nobody\npm = dynamic\npm.max_children = 50\npm.min_spare_servers = 4\npm.max_spare_servers = 32\npm.start_servers = 18\nclear_env = no\n", - "prestart.pl": "#!/usr/bin/env perl\n\nundef $/;\n\nuse strict;\nuse warnings;\n\nuse FindBin;\nuse lib (\"$FindBin::RealBin\");\n\nuse File::Find;\nuse Nixpacks::Config::Template qw(compile_template);\nuse Nixpacks::Util::Logger;\nuse Nixpacks::Util::ChmodRecursive qw(chmod_recursive);\nuse Nixpacks::Util::Laravel qw(is_laravel check_possible_env_errors);\n\nmy $server_logger = Nixpacks::Util::Logger->new(\"server\");\n\nNixpacks::Util::ChmodRecursive::chmod_recursive(\"/app/storage\") if -e \"/app/storage\";\n\nif ($#ARGV != 1) {\n print STDERR \"Usage: $0 \\n\";\n exit 1;\n}\n\nif(Nixpacks::Util::Laravel::is_laravel()) {\n Nixpacks::Util::Laravel::check_possible_env_errors(\"/app\");\n}\n\nNixpacks::Config::Template::compile_template($ARGV[0], $ARGV[1]);\nmy $port = $ENV{\"PORT\"};\n$server_logger->info(\"Server starting on port $port\");" + "scripts/config/template.mjs": "import { readFile, writeFile } from \"fs/promises\";\nimport { getNixPath } from \"../util/nix.mjs\";\n\nconst replaceStr = input =>\n input\n // If statements\n .replaceAll(/\\$if\\s*\\((\\w+)\\)\\s*\\(([^]*?)\\)\\s*else\\s*\\(([^]*?)\\)/gm,\n (_all, condition, value, otherwise) =>\n process.env[condition] ? replaceStr(value) : replaceStr(otherwise)\n )\n // Variables\n .replaceAll(/\\${(\\w+)}/g,\n (_all, name) => process.env[name]\n )\n // Nix paths\n .replaceAll(/\\$!{(\\w+)}/g,\n (_all, exe) => getNixPath(exe)\n )\n\nexport async function compileTemplate(infile, outfile) {\n await writeFile(outfile,\n replaceStr(await readFile(infile, { encoding: 'utf8' })),\n { encoding: 'utf8' })\n}\n", + "scripts/prestart.mjs": "#!/usr/bin/env node\nimport { compileTemplate } from \"./config/template.mjs\";\nimport { e } from \"./util/cmd.mjs\";\nimport { checkEnvErrors, isLaravel } from \"./util/laravel.mjs\";\nimport Logger from \"./util/logger.mjs\";\nimport { access, constants } from 'node:fs/promises'\n\nconst prestartLogger = new Logger('prestart');\nconst serverLogger = new Logger('server');\n\nif (process.argv.length != 4) {\n prestartLogger.error(`Usage: ${process.argv[1]} `)\n process.exit(1);\n}\n\nif (isLaravel()) {\n checkEnvErrors('/app')\n}\n\nawait Promise.all([\n access('/app/storage', constants.R_OK)\n .then(() => e('chmod -R ugo+rw /app/storage'))\n .catch(() => {}),\n compileTemplate(process.argv[2], process.argv[3])\n]).catch(err => prestartLogger.error(err));\n\nserverLogger.info(`Server starting on port ${process.env.PORT}`)\n", + "scripts/util/cmd.mjs": "import { execSync } from \"child_process\";\n\nexport const e = cmd => execSync(cmd).toString().replace('\\n', '');", + "scripts/util/laravel.mjs": "import Logger from \"./logger.mjs\"\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\n\nconst variableHints = {\n 'APP_ENV': 'You should probably set this to `production`.'\n};\n\nconst logger = new Logger('laravel');\n\nexport const isLaravel = () => process.env['IS_LARAVEL'] != null;\n\nfunction checkVariable(name) {\n if (!process.env[name]) {\n let hint =\n `Your app configuration references the ${name} environment variable, but it is not set.`\n + (variableHints[name] ?? '');\n\n logger.warn(hint);\n }\n}\n\nexport async function checkEnvErrors(srcdir) {\n const envRegex = /env\\([\"']([^,]*)[\"']\\)/g;\n const configDir = path.join(srcdir, 'config');\n\n const config =\n (await Promise.all(\n (await fs.readdir(configDir))\n .filter(fileName => fileName.endsWith('.php'))\n .map(fileName => fs.readFile(path.join(configDir, fileName)))\n )).join('');\n\n for (const match of config.matchAll(envRegex)) {\n if (match[1] != 'APP_KEY') checkVariable(match[1]);\n }\n\n if (!process.env.APP_KEY) {\n logger.warn('Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.');\n }\n}\n", + "scripts/util/logger.mjs": "export default class Logger {\n /** @type string */\n #tag;\n\n /**\n * @param {string} tag\n */\n constructor(tag) {\n this.#tag = tag\n }\n\n #log(color, messageType, message, fn = console.log) {\n fn(`\\x1b[${color}m[${this.#tag}:${messageType}]\\x1b[0m ${message}`)\n }\n\n info(message) {\n this.#log(34, 'info', message)\n }\n\n warn(message) {\n this.#log(35, 'warn', message, console.warn)\n }\n\n error(message) {\n this.#log(31, 'error', message, console.error)\n }\n}\n", + "scripts/util/nix.mjs": "import { e } from \"./cmd.mjs\";\n\nexport const getNixPath = (exe) => e(`nix-store -q ${e(`which ${exe}`)}`);\n" }, "phases": { "build": { @@ -46,7 +46,6 @@ expression: plan "name": "setup", "nixPkgs": [ "(php81.withExtensions (pe: pe.enabled ++ []))", - "perl", "nginx", "libmysqlclient", "php81Packages.composer", @@ -63,6 +62,6 @@ expression: plan } }, "start": { - "cmd": "perl /assets/prestart.pl /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" + "cmd": "node /assets/scripts/prestart.mjs /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" } } diff --git a/tests/snapshots/generate_plan_tests__php_laravel_ext_json.snap b/tests/snapshots/generate_plan_tests__php_laravel_ext_json.snap index 91c0b8752..c3f96d6db 100644 --- a/tests/snapshots/generate_plan_tests__php_laravel_ext_json.snap +++ b/tests/snapshots/generate_plan_tests__php_laravel_ext_json.snap @@ -12,14 +12,14 @@ expression: plan "PORT": "80" }, "staticAssets": { - "Nixpacks/Config/Template.pm": "package Nixpacks::Config::Template;\n\nrequire 5.013002;\n\nuse Nixpacks::Nix;\n\nsub if_stmt {\n my ($condition, $value, $else) = @_;\n\n if($ENV{$condition} ne \"\") {\n return replace_str($value);\n } else {\n return replace_str($else);\n }\n}\n\nsub replace_str {\n my ($input) = @_;\n my $new = \n $input \n =~ \n # If statements\n s{\n \\$if\\s*\\((\\w+)\\)\\s*\\(\n ([\\s\\S]*?)\n \\)\\s*else\\s*\\(\n ([\\s\\S]*?)\n \\)\n }{if_stmt($1, $2, $3)}egxr\n =~\n # Variables\n s/\\$\\{(\\w+)\\}/$ENV{$1}/egr\n =~ \n # Nix paths\n s/\\$\\!\\{(\\w+)\\}/Nixpacks::Nix::get_nix_path($1)/egr;\n return $new;\n}\n\nsub compile_template {\n my ($infile, $outfile) = @_;\n open(my $handle, '<', $infile) or die \"Could not open configuration file '$infile' $!\";\n my $out = '';\n while (my $line = <$handle>) {\n $out .= replace_str($line);\n }\n close(FH);\n open(FH, '>', $outfile) or die \"Could not write configuration file '$outfile' $!\";\n print FH $out;\n close(FH);\n}\n\n1;", - "Nixpacks/Nix.pm": "package Nixpacks::Nix;\n\nsub get_nix_path {\n my ($exe) = @_;\n \n my $path = `which $exe`;\n $path =~ s/\\n//;\n my $storePath = `nix-store -q $path`;\n $storePath =~ s/\\n//;\n return $storePath;\n}\n\n1;", - "Nixpacks/Util/ChmodRecursive.pm": "# https://stackoverflow.com/a/3738367\npackage Nixpacks::Util::ChmodRecursive;\n\nuse File::Find;\n\nsub chmod_recursive {\n my ($dir) = @_;\n sub wanted\n {\n my $perm = -d $File::Find::name ? 0777 : 0666;\n chmod $perm, $File::Find::name;\n }\n find(\\&wanted, $dir);\n}\n\n1;", - "Nixpacks/Util/Laravel.pm": "package Nixpacks::Util::Laravel;\n\nuse File::Spec::Functions qw(catfile);\nuse Nixpacks::Util::Logger;\n\nmy %variable_hints = (\n APP_ENV => 'You should probably set it to `production`.',\n);\n\nmy $logger = Nixpacks::Util::Logger->new(\"laravel\");\n\nsub is_laravel {\n $ENV{IS_LARAVEL} ne \"\";\n}\n\nsub check_variable {\n my ($varname) = @_;\n\n if($ENV{$varname} eq \"\") {\n my $hint = \"Your app configuration references the $varname environment variable, but it is not set.\";\n if(defined $variable_hints{$varname}) {\n $hint .= ' ' . $variable_hints{$varname};\n }\n $logger->warn($hint);\n }\n}\n\nsub check_possible_env_errors {\n my ($srcdir) = @_;\n\n my $config_path = catfile($srcdir, 'config', '*.php');\n my @config_files = glob($config_path);\n\n foreach my $config_file (@config_files) {\n open(FH, '<', $config_file);\n\n while() {\n check_variable($1) if /env\\([\"']([^,]*)[\"']\\)/ and $1 ne \"APP_KEY\";\n }\n }\n\t\n\tif($ENV{APP_KEY} eq \"\") {\n\t\t$logger->warn(\"Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.\")\n\t}\n}\n\n1;", - "Nixpacks/Util/Logger.pm": "package Nixpacks::Util::Logger;\n\nsub new {\n my ($class, $tag) = @_;\n my $self = bless { tag => $tag }, $class;\n}\n\nsub log {\n my ($self, $color, $message_type, $message) = @_;\n my $tag = $self->{tag};\n CORE::say \"\\e[${color}m[$tag:$message_type]\\e[0m $message\";\n}\n\nsub info {\n my ($self, $message) = @_;\n $self->log(34, \"info\", $message);\n}\n\nsub warn {\n my ($self, $message) = @_;\n $self->log(33, \"warn\", $message);\n}\n\nsub err {\n my ($self, $message) = @_;\n $self->log(31, \"error\", $message);\n}\n\n1;", "nginx.template.conf": "worker_processes 5;\ndaemon off;\n\nworker_rlimit_nofile 8192;\n\nevents {\n worker_connections 4096; # Default: 1024\n}\n\nhttp {\n include $!{nginx}/conf/mime.types;\n index index.html index.htm index.php;\n\n default_type application/octet-stream;\n log_format main '$remote_addr - $remote_user [$time_local] $status '\n '\"$request\" $body_bytes_sent \"$http_referer\" '\n '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n access_log /dev/stdout;\n error_log /dev/stdout;\n sendfile on;\n tcp_nopush on;\n server_names_hash_bucket_size 128; # this seems to be required for some vhosts\n\n server {\n listen ${PORT};\n listen [::]:${PORT};\n server_name localhost;\n\n $if(NIXPACKS_PHP_ROOT_DIR) (\n root ${NIXPACKS_PHP_ROOT_DIR};\n ) else (\n root /app;\n )\n \n add_header X-Frame-Options \"SAMEORIGIN\";\n add_header X-Content-Type-Options \"nosniff\";\n \n index index.php;\n \n charset utf-8;\n \n $if(IS_LARAVEL) (\n location / {\n try_files $uri $uri/ /index.php?$query_string;\n }\n ) else ()\n \n $if(NIXPACKS_PHP_FALLBACK_PATH) (\n location / {\n try_files $uri $uri/ ${NIXPACKS_PHP_FALLBACK_PATH}?$query_string;\n }\n ) else ()\n \n location = /favicon.ico { access_log off; log_not_found off; }\n location = /robots.txt { access_log off; log_not_found off; }\n \n $if(IS_LARAVEL) (\n error_page 404 /index.php;\n ) else ()\n \n location ~ \\.php$ {\n fastcgi_pass 127.0.0.1:9000;\n fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n include $!{nginx}/conf/fastcgi_params;\n include $!{nginx}/conf/fastcgi.conf;\n }\n \n location ~ /\\.(?!well-known).* {\n deny all;\n }\n }\n}", "php-fpm.conf": "[www]\nlisten = 127.0.0.1:9000\nuser = nobody\npm = dynamic\npm.max_children = 50\npm.min_spare_servers = 4\npm.max_spare_servers = 32\npm.start_servers = 18\nclear_env = no\n", - "prestart.pl": "#!/usr/bin/env perl\n\nundef $/;\n\nuse strict;\nuse warnings;\n\nuse FindBin;\nuse lib (\"$FindBin::RealBin\");\n\nuse File::Find;\nuse Nixpacks::Config::Template qw(compile_template);\nuse Nixpacks::Util::Logger;\nuse Nixpacks::Util::ChmodRecursive qw(chmod_recursive);\nuse Nixpacks::Util::Laravel qw(is_laravel check_possible_env_errors);\n\nmy $server_logger = Nixpacks::Util::Logger->new(\"server\");\n\nNixpacks::Util::ChmodRecursive::chmod_recursive(\"/app/storage\") if -e \"/app/storage\";\n\nif ($#ARGV != 1) {\n print STDERR \"Usage: $0 \\n\";\n exit 1;\n}\n\nif(Nixpacks::Util::Laravel::is_laravel()) {\n Nixpacks::Util::Laravel::check_possible_env_errors(\"/app\");\n}\n\nNixpacks::Config::Template::compile_template($ARGV[0], $ARGV[1]);\nmy $port = $ENV{\"PORT\"};\n$server_logger->info(\"Server starting on port $port\");" + "scripts/config/template.mjs": "import { readFile, writeFile } from \"fs/promises\";\nimport { getNixPath } from \"../util/nix.mjs\";\n\nconst replaceStr = input =>\n input\n // If statements\n .replaceAll(/\\$if\\s*\\((\\w+)\\)\\s*\\(([^]*?)\\)\\s*else\\s*\\(([^]*?)\\)/gm,\n (_all, condition, value, otherwise) =>\n process.env[condition] ? replaceStr(value) : replaceStr(otherwise)\n )\n // Variables\n .replaceAll(/\\${(\\w+)}/g,\n (_all, name) => process.env[name]\n )\n // Nix paths\n .replaceAll(/\\$!{(\\w+)}/g,\n (_all, exe) => getNixPath(exe)\n )\n\nexport async function compileTemplate(infile, outfile) {\n await writeFile(outfile,\n replaceStr(await readFile(infile, { encoding: 'utf8' })),\n { encoding: 'utf8' })\n}\n", + "scripts/prestart.mjs": "#!/usr/bin/env node\nimport { compileTemplate } from \"./config/template.mjs\";\nimport { e } from \"./util/cmd.mjs\";\nimport { checkEnvErrors, isLaravel } from \"./util/laravel.mjs\";\nimport Logger from \"./util/logger.mjs\";\nimport { access, constants } from 'node:fs/promises'\n\nconst prestartLogger = new Logger('prestart');\nconst serverLogger = new Logger('server');\n\nif (process.argv.length != 4) {\n prestartLogger.error(`Usage: ${process.argv[1]} `)\n process.exit(1);\n}\n\nif (isLaravel()) {\n checkEnvErrors('/app')\n}\n\nawait Promise.all([\n access('/app/storage', constants.R_OK)\n .then(() => e('chmod -R ugo+rw /app/storage'))\n .catch(() => {}),\n compileTemplate(process.argv[2], process.argv[3])\n]).catch(err => prestartLogger.error(err));\n\nserverLogger.info(`Server starting on port ${process.env.PORT}`)\n", + "scripts/util/cmd.mjs": "import { execSync } from \"child_process\";\n\nexport const e = cmd => execSync(cmd).toString().replace('\\n', '');", + "scripts/util/laravel.mjs": "import Logger from \"./logger.mjs\"\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\n\nconst variableHints = {\n 'APP_ENV': 'You should probably set this to `production`.'\n};\n\nconst logger = new Logger('laravel');\n\nexport const isLaravel = () => process.env['IS_LARAVEL'] != null;\n\nfunction checkVariable(name) {\n if (!process.env[name]) {\n let hint =\n `Your app configuration references the ${name} environment variable, but it is not set.`\n + (variableHints[name] ?? '');\n\n logger.warn(hint);\n }\n}\n\nexport async function checkEnvErrors(srcdir) {\n const envRegex = /env\\([\"']([^,]*)[\"']\\)/g;\n const configDir = path.join(srcdir, 'config');\n\n const config =\n (await Promise.all(\n (await fs.readdir(configDir))\n .filter(fileName => fileName.endsWith('.php'))\n .map(fileName => fs.readFile(path.join(configDir, fileName)))\n )).join('');\n\n for (const match of config.matchAll(envRegex)) {\n if (match[1] != 'APP_KEY') checkVariable(match[1]);\n }\n\n if (!process.env.APP_KEY) {\n logger.warn('Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.');\n }\n}\n", + "scripts/util/logger.mjs": "export default class Logger {\n /** @type string */\n #tag;\n\n /**\n * @param {string} tag\n */\n constructor(tag) {\n this.#tag = tag\n }\n\n #log(color, messageType, message, fn = console.log) {\n fn(`\\x1b[${color}m[${this.#tag}:${messageType}]\\x1b[0m ${message}`)\n }\n\n info(message) {\n this.#log(34, 'info', message)\n }\n\n warn(message) {\n this.#log(35, 'warn', message, console.warn)\n }\n\n error(message) {\n this.#log(31, 'error', message, console.error)\n }\n}\n", + "scripts/util/nix.mjs": "import { e } from \"./cmd.mjs\";\n\nexport const getNixPath = (exe) => e(`nix-store -q ${e(`which ${exe}`)}`);\n" }, "phases": { "build": { @@ -46,7 +46,6 @@ expression: plan "name": "setup", "nixPkgs": [ "(php81.withExtensions (pe: pe.enabled ++ []))", - "perl", "nginx", "libmysqlclient", "php81Packages.composer", @@ -63,6 +62,6 @@ expression: plan } }, "start": { - "cmd": "perl /assets/prestart.pl /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" + "cmd": "node /assets/scripts/prestart.mjs /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" } } diff --git a/tests/snapshots/generate_plan_tests__php_laravel_ext_mongodb.snap b/tests/snapshots/generate_plan_tests__php_laravel_ext_mongodb.snap index df12e8722..ed65828ae 100644 --- a/tests/snapshots/generate_plan_tests__php_laravel_ext_mongodb.snap +++ b/tests/snapshots/generate_plan_tests__php_laravel_ext_mongodb.snap @@ -12,14 +12,14 @@ expression: plan "PORT": "80" }, "staticAssets": { - "Nixpacks/Config/Template.pm": "package Nixpacks::Config::Template;\n\nrequire 5.013002;\n\nuse Nixpacks::Nix;\n\nsub if_stmt {\n my ($condition, $value, $else) = @_;\n\n if($ENV{$condition} ne \"\") {\n return replace_str($value);\n } else {\n return replace_str($else);\n }\n}\n\nsub replace_str {\n my ($input) = @_;\n my $new = \n $input \n =~ \n # If statements\n s{\n \\$if\\s*\\((\\w+)\\)\\s*\\(\n ([\\s\\S]*?)\n \\)\\s*else\\s*\\(\n ([\\s\\S]*?)\n \\)\n }{if_stmt($1, $2, $3)}egxr\n =~\n # Variables\n s/\\$\\{(\\w+)\\}/$ENV{$1}/egr\n =~ \n # Nix paths\n s/\\$\\!\\{(\\w+)\\}/Nixpacks::Nix::get_nix_path($1)/egr;\n return $new;\n}\n\nsub compile_template {\n my ($infile, $outfile) = @_;\n open(my $handle, '<', $infile) or die \"Could not open configuration file '$infile' $!\";\n my $out = '';\n while (my $line = <$handle>) {\n $out .= replace_str($line);\n }\n close(FH);\n open(FH, '>', $outfile) or die \"Could not write configuration file '$outfile' $!\";\n print FH $out;\n close(FH);\n}\n\n1;", - "Nixpacks/Nix.pm": "package Nixpacks::Nix;\n\nsub get_nix_path {\n my ($exe) = @_;\n \n my $path = `which $exe`;\n $path =~ s/\\n//;\n my $storePath = `nix-store -q $path`;\n $storePath =~ s/\\n//;\n return $storePath;\n}\n\n1;", - "Nixpacks/Util/ChmodRecursive.pm": "# https://stackoverflow.com/a/3738367\npackage Nixpacks::Util::ChmodRecursive;\n\nuse File::Find;\n\nsub chmod_recursive {\n my ($dir) = @_;\n sub wanted\n {\n my $perm = -d $File::Find::name ? 0777 : 0666;\n chmod $perm, $File::Find::name;\n }\n find(\\&wanted, $dir);\n}\n\n1;", - "Nixpacks/Util/Laravel.pm": "package Nixpacks::Util::Laravel;\n\nuse File::Spec::Functions qw(catfile);\nuse Nixpacks::Util::Logger;\n\nmy %variable_hints = (\n APP_ENV => 'You should probably set it to `production`.',\n);\n\nmy $logger = Nixpacks::Util::Logger->new(\"laravel\");\n\nsub is_laravel {\n $ENV{IS_LARAVEL} ne \"\";\n}\n\nsub check_variable {\n my ($varname) = @_;\n\n if($ENV{$varname} eq \"\") {\n my $hint = \"Your app configuration references the $varname environment variable, but it is not set.\";\n if(defined $variable_hints{$varname}) {\n $hint .= ' ' . $variable_hints{$varname};\n }\n $logger->warn($hint);\n }\n}\n\nsub check_possible_env_errors {\n my ($srcdir) = @_;\n\n my $config_path = catfile($srcdir, 'config', '*.php');\n my @config_files = glob($config_path);\n\n foreach my $config_file (@config_files) {\n open(FH, '<', $config_file);\n\n while() {\n check_variable($1) if /env\\([\"']([^,]*)[\"']\\)/ and $1 ne \"APP_KEY\";\n }\n }\n\t\n\tif($ENV{APP_KEY} eq \"\") {\n\t\t$logger->warn(\"Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.\")\n\t}\n}\n\n1;", - "Nixpacks/Util/Logger.pm": "package Nixpacks::Util::Logger;\n\nsub new {\n my ($class, $tag) = @_;\n my $self = bless { tag => $tag }, $class;\n}\n\nsub log {\n my ($self, $color, $message_type, $message) = @_;\n my $tag = $self->{tag};\n CORE::say \"\\e[${color}m[$tag:$message_type]\\e[0m $message\";\n}\n\nsub info {\n my ($self, $message) = @_;\n $self->log(34, \"info\", $message);\n}\n\nsub warn {\n my ($self, $message) = @_;\n $self->log(33, \"warn\", $message);\n}\n\nsub err {\n my ($self, $message) = @_;\n $self->log(31, \"error\", $message);\n}\n\n1;", "nginx.template.conf": "worker_processes 5;\ndaemon off;\n\nworker_rlimit_nofile 8192;\n\nevents {\n worker_connections 4096; # Default: 1024\n}\n\nhttp {\n include $!{nginx}/conf/mime.types;\n index index.html index.htm index.php;\n\n default_type application/octet-stream;\n log_format main '$remote_addr - $remote_user [$time_local] $status '\n '\"$request\" $body_bytes_sent \"$http_referer\" '\n '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n access_log /dev/stdout;\n error_log /dev/stdout;\n sendfile on;\n tcp_nopush on;\n server_names_hash_bucket_size 128; # this seems to be required for some vhosts\n\n server {\n listen ${PORT};\n listen [::]:${PORT};\n server_name localhost;\n\n $if(NIXPACKS_PHP_ROOT_DIR) (\n root ${NIXPACKS_PHP_ROOT_DIR};\n ) else (\n root /app;\n )\n \n add_header X-Frame-Options \"SAMEORIGIN\";\n add_header X-Content-Type-Options \"nosniff\";\n \n index index.php;\n \n charset utf-8;\n \n $if(IS_LARAVEL) (\n location / {\n try_files $uri $uri/ /index.php?$query_string;\n }\n ) else ()\n \n $if(NIXPACKS_PHP_FALLBACK_PATH) (\n location / {\n try_files $uri $uri/ ${NIXPACKS_PHP_FALLBACK_PATH}?$query_string;\n }\n ) else ()\n \n location = /favicon.ico { access_log off; log_not_found off; }\n location = /robots.txt { access_log off; log_not_found off; }\n \n $if(IS_LARAVEL) (\n error_page 404 /index.php;\n ) else ()\n \n location ~ \\.php$ {\n fastcgi_pass 127.0.0.1:9000;\n fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n include $!{nginx}/conf/fastcgi_params;\n include $!{nginx}/conf/fastcgi.conf;\n }\n \n location ~ /\\.(?!well-known).* {\n deny all;\n }\n }\n}", "php-fpm.conf": "[www]\nlisten = 127.0.0.1:9000\nuser = nobody\npm = dynamic\npm.max_children = 50\npm.min_spare_servers = 4\npm.max_spare_servers = 32\npm.start_servers = 18\nclear_env = no\n", - "prestart.pl": "#!/usr/bin/env perl\n\nundef $/;\n\nuse strict;\nuse warnings;\n\nuse FindBin;\nuse lib (\"$FindBin::RealBin\");\n\nuse File::Find;\nuse Nixpacks::Config::Template qw(compile_template);\nuse Nixpacks::Util::Logger;\nuse Nixpacks::Util::ChmodRecursive qw(chmod_recursive);\nuse Nixpacks::Util::Laravel qw(is_laravel check_possible_env_errors);\n\nmy $server_logger = Nixpacks::Util::Logger->new(\"server\");\n\nNixpacks::Util::ChmodRecursive::chmod_recursive(\"/app/storage\") if -e \"/app/storage\";\n\nif ($#ARGV != 1) {\n print STDERR \"Usage: $0 \\n\";\n exit 1;\n}\n\nif(Nixpacks::Util::Laravel::is_laravel()) {\n Nixpacks::Util::Laravel::check_possible_env_errors(\"/app\");\n}\n\nNixpacks::Config::Template::compile_template($ARGV[0], $ARGV[1]);\nmy $port = $ENV{\"PORT\"};\n$server_logger->info(\"Server starting on port $port\");" + "scripts/config/template.mjs": "import { readFile, writeFile } from \"fs/promises\";\nimport { getNixPath } from \"../util/nix.mjs\";\n\nconst replaceStr = input =>\n input\n // If statements\n .replaceAll(/\\$if\\s*\\((\\w+)\\)\\s*\\(([^]*?)\\)\\s*else\\s*\\(([^]*?)\\)/gm,\n (_all, condition, value, otherwise) =>\n process.env[condition] ? replaceStr(value) : replaceStr(otherwise)\n )\n // Variables\n .replaceAll(/\\${(\\w+)}/g,\n (_all, name) => process.env[name]\n )\n // Nix paths\n .replaceAll(/\\$!{(\\w+)}/g,\n (_all, exe) => getNixPath(exe)\n )\n\nexport async function compileTemplate(infile, outfile) {\n await writeFile(outfile,\n replaceStr(await readFile(infile, { encoding: 'utf8' })),\n { encoding: 'utf8' })\n}\n", + "scripts/prestart.mjs": "#!/usr/bin/env node\nimport { compileTemplate } from \"./config/template.mjs\";\nimport { e } from \"./util/cmd.mjs\";\nimport { checkEnvErrors, isLaravel } from \"./util/laravel.mjs\";\nimport Logger from \"./util/logger.mjs\";\nimport { access, constants } from 'node:fs/promises'\n\nconst prestartLogger = new Logger('prestart');\nconst serverLogger = new Logger('server');\n\nif (process.argv.length != 4) {\n prestartLogger.error(`Usage: ${process.argv[1]} `)\n process.exit(1);\n}\n\nif (isLaravel()) {\n checkEnvErrors('/app')\n}\n\nawait Promise.all([\n access('/app/storage', constants.R_OK)\n .then(() => e('chmod -R ugo+rw /app/storage'))\n .catch(() => {}),\n compileTemplate(process.argv[2], process.argv[3])\n]).catch(err => prestartLogger.error(err));\n\nserverLogger.info(`Server starting on port ${process.env.PORT}`)\n", + "scripts/util/cmd.mjs": "import { execSync } from \"child_process\";\n\nexport const e = cmd => execSync(cmd).toString().replace('\\n', '');", + "scripts/util/laravel.mjs": "import Logger from \"./logger.mjs\"\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\n\nconst variableHints = {\n 'APP_ENV': 'You should probably set this to `production`.'\n};\n\nconst logger = new Logger('laravel');\n\nexport const isLaravel = () => process.env['IS_LARAVEL'] != null;\n\nfunction checkVariable(name) {\n if (!process.env[name]) {\n let hint =\n `Your app configuration references the ${name} environment variable, but it is not set.`\n + (variableHints[name] ?? '');\n\n logger.warn(hint);\n }\n}\n\nexport async function checkEnvErrors(srcdir) {\n const envRegex = /env\\([\"']([^,]*)[\"']\\)/g;\n const configDir = path.join(srcdir, 'config');\n\n const config =\n (await Promise.all(\n (await fs.readdir(configDir))\n .filter(fileName => fileName.endsWith('.php'))\n .map(fileName => fs.readFile(path.join(configDir, fileName)))\n )).join('');\n\n for (const match of config.matchAll(envRegex)) {\n if (match[1] != 'APP_KEY') checkVariable(match[1]);\n }\n\n if (!process.env.APP_KEY) {\n logger.warn('Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.');\n }\n}\n", + "scripts/util/logger.mjs": "export default class Logger {\n /** @type string */\n #tag;\n\n /**\n * @param {string} tag\n */\n constructor(tag) {\n this.#tag = tag\n }\n\n #log(color, messageType, message, fn = console.log) {\n fn(`\\x1b[${color}m[${this.#tag}:${messageType}]\\x1b[0m ${message}`)\n }\n\n info(message) {\n this.#log(34, 'info', message)\n }\n\n warn(message) {\n this.#log(35, 'warn', message, console.warn)\n }\n\n error(message) {\n this.#log(31, 'error', message, console.error)\n }\n}\n", + "scripts/util/nix.mjs": "import { e } from \"./cmd.mjs\";\n\nexport const getNixPath = (exe) => e(`nix-store -q ${e(`which ${exe}`)}`);\n" }, "phases": { "build": { @@ -46,7 +46,6 @@ expression: plan "name": "setup", "nixPkgs": [ "(php81.withExtensions (pe: pe.enabled ++ [pe.all.mongodb]))", - "perl", "nginx", "libmysqlclient", "php81Packages.composer", @@ -65,6 +64,6 @@ expression: plan } }, "start": { - "cmd": "perl /assets/prestart.pl /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" + "cmd": "node /assets/scripts/prestart.mjs /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" } } diff --git a/tests/snapshots/generate_plan_tests__php_vanilla.snap b/tests/snapshots/generate_plan_tests__php_vanilla.snap index addd950a3..e29909a15 100644 --- a/tests/snapshots/generate_plan_tests__php_vanilla.snap +++ b/tests/snapshots/generate_plan_tests__php_vanilla.snap @@ -10,14 +10,14 @@ expression: plan "PORT": "80" }, "staticAssets": { - "Nixpacks/Config/Template.pm": "package Nixpacks::Config::Template;\n\nrequire 5.013002;\n\nuse Nixpacks::Nix;\n\nsub if_stmt {\n my ($condition, $value, $else) = @_;\n\n if($ENV{$condition} ne \"\") {\n return replace_str($value);\n } else {\n return replace_str($else);\n }\n}\n\nsub replace_str {\n my ($input) = @_;\n my $new = \n $input \n =~ \n # If statements\n s{\n \\$if\\s*\\((\\w+)\\)\\s*\\(\n ([\\s\\S]*?)\n \\)\\s*else\\s*\\(\n ([\\s\\S]*?)\n \\)\n }{if_stmt($1, $2, $3)}egxr\n =~\n # Variables\n s/\\$\\{(\\w+)\\}/$ENV{$1}/egr\n =~ \n # Nix paths\n s/\\$\\!\\{(\\w+)\\}/Nixpacks::Nix::get_nix_path($1)/egr;\n return $new;\n}\n\nsub compile_template {\n my ($infile, $outfile) = @_;\n open(my $handle, '<', $infile) or die \"Could not open configuration file '$infile' $!\";\n my $out = '';\n while (my $line = <$handle>) {\n $out .= replace_str($line);\n }\n close(FH);\n open(FH, '>', $outfile) or die \"Could not write configuration file '$outfile' $!\";\n print FH $out;\n close(FH);\n}\n\n1;", - "Nixpacks/Nix.pm": "package Nixpacks::Nix;\n\nsub get_nix_path {\n my ($exe) = @_;\n \n my $path = `which $exe`;\n $path =~ s/\\n//;\n my $storePath = `nix-store -q $path`;\n $storePath =~ s/\\n//;\n return $storePath;\n}\n\n1;", - "Nixpacks/Util/ChmodRecursive.pm": "# https://stackoverflow.com/a/3738367\npackage Nixpacks::Util::ChmodRecursive;\n\nuse File::Find;\n\nsub chmod_recursive {\n my ($dir) = @_;\n sub wanted\n {\n my $perm = -d $File::Find::name ? 0777 : 0666;\n chmod $perm, $File::Find::name;\n }\n find(\\&wanted, $dir);\n}\n\n1;", - "Nixpacks/Util/Laravel.pm": "package Nixpacks::Util::Laravel;\n\nuse File::Spec::Functions qw(catfile);\nuse Nixpacks::Util::Logger;\n\nmy %variable_hints = (\n APP_ENV => 'You should probably set it to `production`.',\n);\n\nmy $logger = Nixpacks::Util::Logger->new(\"laravel\");\n\nsub is_laravel {\n $ENV{IS_LARAVEL} ne \"\";\n}\n\nsub check_variable {\n my ($varname) = @_;\n\n if($ENV{$varname} eq \"\") {\n my $hint = \"Your app configuration references the $varname environment variable, but it is not set.\";\n if(defined $variable_hints{$varname}) {\n $hint .= ' ' . $variable_hints{$varname};\n }\n $logger->warn($hint);\n }\n}\n\nsub check_possible_env_errors {\n my ($srcdir) = @_;\n\n my $config_path = catfile($srcdir, 'config', '*.php');\n my @config_files = glob($config_path);\n\n foreach my $config_file (@config_files) {\n open(FH, '<', $config_file);\n\n while() {\n check_variable($1) if /env\\([\"']([^,]*)[\"']\\)/ and $1 ne \"APP_KEY\";\n }\n }\n\t\n\tif($ENV{APP_KEY} eq \"\") {\n\t\t$logger->warn(\"Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.\")\n\t}\n}\n\n1;", - "Nixpacks/Util/Logger.pm": "package Nixpacks::Util::Logger;\n\nsub new {\n my ($class, $tag) = @_;\n my $self = bless { tag => $tag }, $class;\n}\n\nsub log {\n my ($self, $color, $message_type, $message) = @_;\n my $tag = $self->{tag};\n CORE::say \"\\e[${color}m[$tag:$message_type]\\e[0m $message\";\n}\n\nsub info {\n my ($self, $message) = @_;\n $self->log(34, \"info\", $message);\n}\n\nsub warn {\n my ($self, $message) = @_;\n $self->log(33, \"warn\", $message);\n}\n\nsub err {\n my ($self, $message) = @_;\n $self->log(31, \"error\", $message);\n}\n\n1;", "nginx.template.conf": "worker_processes 5;\ndaemon off;\n\nworker_rlimit_nofile 8192;\n\nevents {\n worker_connections 4096; # Default: 1024\n}\n\nhttp {\n include $!{nginx}/conf/mime.types;\n index index.html index.htm index.php;\n\n default_type application/octet-stream;\n log_format main '$remote_addr - $remote_user [$time_local] $status '\n '\"$request\" $body_bytes_sent \"$http_referer\" '\n '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n access_log /dev/stdout;\n error_log /dev/stdout;\n sendfile on;\n tcp_nopush on;\n server_names_hash_bucket_size 128; # this seems to be required for some vhosts\n\n server {\n listen ${PORT};\n listen [::]:${PORT};\n server_name localhost;\n\n $if(NIXPACKS_PHP_ROOT_DIR) (\n root ${NIXPACKS_PHP_ROOT_DIR};\n ) else (\n root /app;\n )\n \n add_header X-Frame-Options \"SAMEORIGIN\";\n add_header X-Content-Type-Options \"nosniff\";\n \n index index.php;\n \n charset utf-8;\n \n $if(IS_LARAVEL) (\n location / {\n try_files $uri $uri/ /index.php?$query_string;\n }\n ) else ()\n \n $if(NIXPACKS_PHP_FALLBACK_PATH) (\n location / {\n try_files $uri $uri/ ${NIXPACKS_PHP_FALLBACK_PATH}?$query_string;\n }\n ) else ()\n \n location = /favicon.ico { access_log off; log_not_found off; }\n location = /robots.txt { access_log off; log_not_found off; }\n \n $if(IS_LARAVEL) (\n error_page 404 /index.php;\n ) else ()\n \n location ~ \\.php$ {\n fastcgi_pass 127.0.0.1:9000;\n fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n include $!{nginx}/conf/fastcgi_params;\n include $!{nginx}/conf/fastcgi.conf;\n }\n \n location ~ /\\.(?!well-known).* {\n deny all;\n }\n }\n}", "php-fpm.conf": "[www]\nlisten = 127.0.0.1:9000\nuser = nobody\npm = dynamic\npm.max_children = 50\npm.min_spare_servers = 4\npm.max_spare_servers = 32\npm.start_servers = 18\nclear_env = no\n", - "prestart.pl": "#!/usr/bin/env perl\n\nundef $/;\n\nuse strict;\nuse warnings;\n\nuse FindBin;\nuse lib (\"$FindBin::RealBin\");\n\nuse File::Find;\nuse Nixpacks::Config::Template qw(compile_template);\nuse Nixpacks::Util::Logger;\nuse Nixpacks::Util::ChmodRecursive qw(chmod_recursive);\nuse Nixpacks::Util::Laravel qw(is_laravel check_possible_env_errors);\n\nmy $server_logger = Nixpacks::Util::Logger->new(\"server\");\n\nNixpacks::Util::ChmodRecursive::chmod_recursive(\"/app/storage\") if -e \"/app/storage\";\n\nif ($#ARGV != 1) {\n print STDERR \"Usage: $0 \\n\";\n exit 1;\n}\n\nif(Nixpacks::Util::Laravel::is_laravel()) {\n Nixpacks::Util::Laravel::check_possible_env_errors(\"/app\");\n}\n\nNixpacks::Config::Template::compile_template($ARGV[0], $ARGV[1]);\nmy $port = $ENV{\"PORT\"};\n$server_logger->info(\"Server starting on port $port\");" + "scripts/config/template.mjs": "import { readFile, writeFile } from \"fs/promises\";\nimport { getNixPath } from \"../util/nix.mjs\";\n\nconst replaceStr = input =>\n input\n // If statements\n .replaceAll(/\\$if\\s*\\((\\w+)\\)\\s*\\(([^]*?)\\)\\s*else\\s*\\(([^]*?)\\)/gm,\n (_all, condition, value, otherwise) =>\n process.env[condition] ? replaceStr(value) : replaceStr(otherwise)\n )\n // Variables\n .replaceAll(/\\${(\\w+)}/g,\n (_all, name) => process.env[name]\n )\n // Nix paths\n .replaceAll(/\\$!{(\\w+)}/g,\n (_all, exe) => getNixPath(exe)\n )\n\nexport async function compileTemplate(infile, outfile) {\n await writeFile(outfile,\n replaceStr(await readFile(infile, { encoding: 'utf8' })),\n { encoding: 'utf8' })\n}\n", + "scripts/prestart.mjs": "#!/usr/bin/env node\nimport { compileTemplate } from \"./config/template.mjs\";\nimport { e } from \"./util/cmd.mjs\";\nimport { checkEnvErrors, isLaravel } from \"./util/laravel.mjs\";\nimport Logger from \"./util/logger.mjs\";\nimport { access, constants } from 'node:fs/promises'\n\nconst prestartLogger = new Logger('prestart');\nconst serverLogger = new Logger('server');\n\nif (process.argv.length != 4) {\n prestartLogger.error(`Usage: ${process.argv[1]} `)\n process.exit(1);\n}\n\nif (isLaravel()) {\n checkEnvErrors('/app')\n}\n\nawait Promise.all([\n access('/app/storage', constants.R_OK)\n .then(() => e('chmod -R ugo+rw /app/storage'))\n .catch(() => {}),\n compileTemplate(process.argv[2], process.argv[3])\n]).catch(err => prestartLogger.error(err));\n\nserverLogger.info(`Server starting on port ${process.env.PORT}`)\n", + "scripts/util/cmd.mjs": "import { execSync } from \"child_process\";\n\nexport const e = cmd => execSync(cmd).toString().replace('\\n', '');", + "scripts/util/laravel.mjs": "import Logger from \"./logger.mjs\"\nimport * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\n\nconst variableHints = {\n 'APP_ENV': 'You should probably set this to `production`.'\n};\n\nconst logger = new Logger('laravel');\n\nexport const isLaravel = () => process.env['IS_LARAVEL'] != null;\n\nfunction checkVariable(name) {\n if (!process.env[name]) {\n let hint =\n `Your app configuration references the ${name} environment variable, but it is not set.`\n + (variableHints[name] ?? '');\n\n logger.warn(hint);\n }\n}\n\nexport async function checkEnvErrors(srcdir) {\n const envRegex = /env\\([\"']([^,]*)[\"']\\)/g;\n const configDir = path.join(srcdir, 'config');\n\n const config =\n (await Promise.all(\n (await fs.readdir(configDir))\n .filter(fileName => fileName.endsWith('.php'))\n .map(fileName => fs.readFile(path.join(configDir, fileName)))\n )).join('');\n\n for (const match of config.matchAll(envRegex)) {\n if (match[1] != 'APP_KEY') checkVariable(match[1]);\n }\n\n if (!process.env.APP_KEY) {\n logger.warn('Your app key is not set! Please set a random 32-character string in your APP_KEY environment variable. This can be easily generated with `openssl rand -hex 16`.');\n }\n}\n", + "scripts/util/logger.mjs": "export default class Logger {\n /** @type string */\n #tag;\n\n /**\n * @param {string} tag\n */\n constructor(tag) {\n this.#tag = tag\n }\n\n #log(color, messageType, message, fn = console.log) {\n fn(`\\x1b[${color}m[${this.#tag}:${messageType}]\\x1b[0m ${message}`)\n }\n\n info(message) {\n this.#log(34, 'info', message)\n }\n\n warn(message) {\n this.#log(35, 'warn', message, console.warn)\n }\n\n error(message) {\n this.#log(31, 'error', message, console.error)\n }\n}\n", + "scripts/util/nix.mjs": "import { e } from \"./cmd.mjs\";\n\nexport const getNixPath = (exe) => e(`nix-store -q ${e(`which ${exe}`)}`);\n" }, "phases": { "install": { @@ -33,19 +33,22 @@ expression: plan "name": "setup", "nixPkgs": [ "(php.withExtensions (pe: pe.enabled ++ []))", - "perl", "nginx", "libmysqlclient", - "phpPackages.composer" + "phpPackages.composer", + "nodejs_18", + "npm-9_x" ], "nixLibs": [ "libmysqlclient" ], - "nixOverlays": [], + "nixOverlays": [ + "https://github.com/railwayapp/nix-npm-overlay/archive/main.tar.gz" + ], "nixpkgsArchive": "[archive]" } }, "start": { - "cmd": "perl /assets/prestart.pl /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" + "cmd": "node /assets/scripts/prestart.mjs /assets/nginx.template.conf /nginx.conf && (php-fpm -y /assets/php-fpm.conf & nginx -c /nginx.conf)" } }