
# script_nodejs_desc()
sub script_nodejs_desc
{
return "Node.js";
}

sub script_nodejs_uses
{
return ( "node", "proxy" );
}

sub script_nodejs_longdesc
{
return "Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications";
}

# script_nodejs_versions()
sub script_nodejs_versions
{
return ( "19.5.0", "18.13.0", "16.19.0" );
}

sub script_nodejs_version_desc
{
local ($ver) = @_;
return &compare_versions($ver, 19) < 0 ? "$ver (LTS)" : "$ver";
}

sub script_nodejs_release
{
return 3;	# Add back version 17.9.0
}

sub script_nodejs_category
{
return "Development";
}

# script_nodejs_depends(&domain, version)
# Make sure we are on Linux
sub script_nodejs_depends
{
local ($d, $ver) = @_;
local @rv;
if ($gconfig{'os_type'} !~ /-linux$/) {
	push(@rv, "Node.js binaries are only available on Linux");
	}
if ($ver >= 10) {
	local $u = &backquote_command("uname -m");
	$u =~ /x86_64/ || push(@rv, "Node.js versions 10 and above are only ".
				    "available on 64-bit systems");
	}
if ($ver >= 18) {
	my $ldd = &backquote_command("ldd --version");
	my ($ldd_ver) = $ldd =~ /([\d\.]+)/;
	if ($ldd_ver < 2.27) {
		push(@rv, "Node.js versions 18 and above require ".
		          "GNU libc version 2.28 and above, while this ".
		          "system version is $ldd_ver");
		}
	}
return @rv;
}

# script_nodejs_params(&domain, version, &upgrade-info)
# Returns HTML for table rows for options for installing PHP-NUKE
sub script_nodejs_params
{
local ($d, $ver, $upgrade) = @_;
local $rv;
local $hdir = &public_html_dir($d, 1);
if ($upgrade) {
	# Options are fixed when upgrading
	local $dir = $upgrade->{'opts'}->{'dir'};
	$dir =~ s/^$d->{'home'}\///;
	$rv .= &ui_table_row("Install directory", $dir);
	}
else {
	# Show editable install options
	$rv .= &ui_table_row("Install sub-directory under <tt>$hdir</tt>",
			     &ui_opt_textbox("dir", &substitute_scriptname_template("nodejs", $d), 30, "At top level"));
	}
return $rv;
}

# script_nodejs_parse(&domain, version, &in, &upgrade-info)
# Returns either a hash ref of parsed options, or an error string
sub script_nodejs_parse
{
local ($d, $ver, $in, $upgrade) = @_;
if ($upgrade) {
	# Options are always the same
	return $upgrade->{'opts'};
	}
else {
	local $hdir = &public_html_dir($d, 0);
	$in->{'dir_def'} || $in->{'dir'} =~ /\S/ && $in->{'dir'} !~ /\.\./ ||
		return "Missing or invalid installation directory";
	local $dir = $in->{'dir_def'} ? $hdir : "$hdir/$in->{'dir'}";
	return { 'dir' => $dir,
		 'path' => $in->{'dir_def'} ? "/" : "/$in->{'dir'}", };
	}
}

# script_nodejs_check(&domain, version, &opts, &upgrade-info)
# Returns an error message if a required option is missing or invalid
sub script_nodejs_check
{
local ($d, $ver, $opts, $upgrade) = @_;
if (-r "$opts->{'dir'}/bin/node") {
	return "Node.js appears to be already installed in the selected directory";
	}
$opts->{'mongrels'} ||= 1;
return undef;
}

# script_nodejs_files(&domain, version, &opts, &upgrade-info)
# Returns a list of files needed by Rails, each of which is a hash ref
# containing a name, filename and URL
sub script_nodejs_files
{
local ($d, $ver, $opts, $upgrade) = @_;
local $u = &backquote_command("uname -m");
local $arch = $u =~ /x86_64/ ? "x64" : "x86";
local @files = ( { 'name' => "source",
	   'file' => "node-v$ver-linux-$arch.tar.gz",
	   'url' => "http://nodejs.org/dist/v$ver/node-v$ver-linux-$arch.tar.gz" } );
return @files;
}

# script_nodejs_install(&domain, version, &opts, &files, &upgrade-info)
# Actually installs PhpWiki, and returns either 1 and an informational
# message, or 0 and an error
sub script_nodejs_install
{
local ($d, $version, $opts, $files, $upgrade) = @_;
local ($out, $ex);

# Stop running server if upgrading
if ($upgrade) {
	&script_nodejs_stop_server($d, $opts);
	}

# Extract tar file to temp dir and copy to target
local $temp = &transname();
local $err = &extract_script_archive($files->{'source'}, $temp, $d,
                                     $opts->{'dir'}, "node-*");
$err && return (0, "Failed to extract binaries : $err");

# Pick a free port
my $port;
if ($upgrade) {
	$port = $opts->{'port'};
	}
else {
	$port = &allocate_mongrel_port(undef, 1);
	$opts->{'port'} = $port;
	}

# Create sample app
if (!$upgrade) {
	my $sample = "$opts->{'dir'}/server.js";
	&open_tempfile_as_domain_user($d, SAMPLE, ">$sample");
	&print_tempfile(SAMPLE, "
	var http = require('http');
	http.createServer(function (req, res) {
	  res.writeHead(200, {'Content-Type': 'text/html'});
	  res.end('Hello World<p>\\n');
	}).listen($port, '127.0.0.1');
	");
	&close_tempfile_as_domain_user($d, SAMPLE);
	}

# Pick a PID file and start command
my $pidfile = "$opts->{'dir'}/node.pid";
$opts->{'pidfile'} = $pidfile;
my ($cmd) = &get_nodejs_start_cmd($d, $opts);
my $userd = $d->{'parent'} ? &get_domain($d->{'parent'}) : $d;
if (&foreign_installed("init") && $userd && $userd->{'unix'} && !$upgrade) {
	# Create init script and start it
	my %cmds_abs = (
		'kill', &has_command('kill'),
	);
	&foreign_require("init");
	&init::enable_at_boot(
		"nodejs-$d->{'dom'}-$port",
		"Start Node.js server for $d->{'dom'}",
		$cmd,
		undef,
		undef,
		{ 'opts' => {
		  'user'   => $userd->{'uid'},
		  'group'  => $userd->{'gid'},
		  'stop'   => "$cmds_abs{'kill'} \$MAINPID",
		  'reload' => " ",
		  'logstd' => "$opts->{'dir'}/node.log",
		  'logerr' => "$opts->{'dir'}/node.err"
		}},
		);
	&init::start_action("nodejs-$d->{'dom'}-$port");
	}
else {
	# Start up node on the script, as the domain user
	&script_nodejs_start_server($d, $opts);
	}

if (!$upgrade) {
	# Configure Apache to proxy to it
	&setup_mongrel_proxy($d, $opts->{'path'}, $port);
	}

local $url = &script_path_url($d, $opts);
local $rp = $opts->{'dir'};
$rp =~ s/^$d->{'home'}\///;
return (1, "Initial Node.js installation complete. Go to <a target=_blank href='$url'>$url</a> to see the sample application. Node is a development environment, so it doesn't do anything by itself!", "Under $rp", $url);
}

# script_nodejs_uninstall(&domain, version, &opts)
# Un-installs a Rails installation, by deleting the directory and database.
# Returns 1 on success and a message, or 0 on failure and an error
sub script_nodejs_uninstall
{
local ($d, $version, $opts) = @_;

# Shut down node server, if running
&script_nodejs_stop_server($d, $opts);

# Remove bootup script
&foreign_require("init");
my $name = "nodejs-$d->{'dom'}-$opts->{'port'}";
&init::delete_at_boot($name);

# Remove the contents of the target directory
local $derr = &delete_script_install_directory($d, $opts);
return (0, $derr) if ($derr);

# Remove proxy Apache config entry for /nodejs
&delete_mongrel_proxy($d, $opts->{'path'});

return (1, "Node.js directory deleted.");
}

sub script_nodejs_start_server
{
local ($d, $opts) = @_;
&foreign_require("init");
my $name = "nodejs-$d->{'dom'}-$opts->{'port'}";
if (&init::action_status($name)) {
	&init::start_action($name);
	&init::enable_at_boot($name);
	}
else {
	my ($cmd, $cmd2, $cmd3) = &get_nodejs_start_cmd($d, $opts);
	$cmd .= " $cmd2 $cmd3";
	&run_as_domain_user($d, $cmd, 1);
	}
}

# Return the PID if the node server is running
sub script_nodejs_status_server
{
local ($d, $opts) = @_;
&foreign_require("init");
my $name = "nodejs-$d->{'dom'}-$opts->{'port'}";
if (&init::action_status($name)) {
	my $status = &init::status_action($name);
	return $status ? ( $status ) : ( );
	}
else {
	my $pid;
	if ($opts->{'pidfile'}) {
		$pid = &check_pid_file($opts->{'pidfile'});
		}
	return $pid ? ( $pid ) : ( );
	}
}

# script_nodejs_stop_server(&domain, &opts)
# Kill the running Node server process
sub script_nodejs_stop_server
{
local ($d, $opts) = @_;
&foreign_require("init");
my $name = "nodejs-$d->{'dom'}-$opts->{'port'}";
if (&init::action_status($name)) {
	&init::stop_action($name);
	&init::disable_at_boot($name);
	}
else {
	my %cmds_abs = (
		'kill', &has_command('kill'),
	);
	if ($opts->{'pidfile'}) {
		local $pid = &check_pid_file($opts->{'pidfile'});
		if ($pid) {
			&run_as_domain_user($d, "$cmds_abs{'kill'} -9 $pid");
			&unlink_file_as_domain_user($d, $opts->{'pidfile'});
			}
		}
	}
}

# script_nodejs_realversion(&domain, &opts)
# Returns the real version number of some script install, or undef if unknown
sub script_nodejs_realversion
{
local ($d, $opts, $sinfo) = @_;
local $out = &backquote_command(
	"$opts->{'dir'}/bin/node --version 2>&1 </dev/null");
if ($out =~ /v([0-9\.]+)/) {
	return $1;
	}
return undef;
}

sub script_nodejs_latest
{
local ($ver) = @_;
if (&compare_versions($ver, 19) >= 0) {
	return ( "http://nodejs.org/en/download/current/",
		 "Current Version: <strong>([0-9\\.]+)" );
	}
elsif (&compare_versions($ver, 18) >= 0) {
	return ( "http://nodejs.org/en/download/",
		 "LTS Version: <strong>([0-9\\.]+)" );
	}
else {
	# Keep older versions around, as 18.x requires a later libc++
	return ( );
	}
}

sub script_nodejs_site
{
return 'http://nodejs.org/';
}

sub get_nodejs_start_cmd
{
my ($d, $opts) = @_;
my %cmds_abs = (
	'echo', &has_command('echo'),
);
return ("$opts->{'dir'}/bin/node $opts->{'dir'}/server.js", "> $opts->{'dir'}/node.log 2>&1 </dev/null", "& $cmds_abs{'echo'} \$! >$opts->{'pidfile'}");
}

1;

