Skip to content
Permalink
master
Go to file
@jhthorsen
Latest commit 9d3cc52 Nov 29, 2020 History
2 contributors

Users who have contributed to this file

@jhthorsen @alilles
executable file 371 lines (298 sloc) 10.5 KB
#!/usr/bin/env perl
BEGIN {
if ($ENV{SNAP} and $ENV{SNAP_USER_COMMON}) {
$ENV{CONVOS_HOME} ||= $ENV{SNAP_USER_COMMON};
@INC = map {
my $local = "$ENV{SNAP}$_"; # Example: /snap/convos/x45/usr/share/perl5
warn "INC: $local / $_\n" if $ENV{CONVOS_SNAP_DEBUG};
-e $local ? $local : $_;
} @INC;
}
}
# IMPORTANT: Cannot load Mojo::File or any other module before they are installed
use feature 'state';
use strict;
use warnings;
use Config;
use Cwd ();
use File::Spec;
use FindBin;
our $VERSION = '5.06';
# Run the program
(bless {}, __PACKAGE__)->run(@ARGV);
# Need to be first, because it is called without parenthesis
sub path_to {
state $home = Cwd::realpath(File::Spec->catdir($FindBin::Bin, File::Spec->updir));
return File::Spec->catfile($home, @_);
}
sub command_build {
my ($script, @cmd) = @_;
$ENV{BUILD_ASSETS} = 1;
$ENV{RELEASE} //= @cmd && $cmd[0] eq 'release';
my @tests = qw(t/production-resources.t);
push @tests, 't/version.t' if $ENV{RELEASE};
$script->command_exec(qw(prove -l), @tests);
}
sub command_cpanm {
my ($script, @cmd) = @_;
state $cpanm
= do { my $cpanm = path_to 'script', 'cpanm'; -r $cpanm ? [$^X, $cpanm] : ['cpanm'] };
$ENV{CPAN_MIRROR} //= 'https://cpan.metacpan.org' if eval 'require IO::Socket::SSL;1';
unshift @cmd, -l => $ENV{CONVOS_LOCAL_LIB};
unshift @cmd, -M => $ENV{CPAN_MIRROR} if $ENV{CPAN_MIRROR};
$script->command_exec(@$cpanm, @cmd);
}
sub command_dev {
my $script = shift;
my @cmd = $script->_cmd_with_secure_listen(@_);
$ENV{$_} //= 1 for qw(LINK_EMBEDDER_ALLOW_INSECURE_SSL CONVOS_DEBUG);
push @cmd, qw(-w lib -w public/convos-api.yaml -w templates) unless grep {/^-?-w/} @cmd;
warn "\$ script/convos webpack @cmd\n";
exec $^X => qw(script/convos), webpack => @cmd;
die "Could not exec $^X script/convos webpack @cmd: $!\n";
}
sub command_exec {
my ($script, @cmd) = @_;
return $script->command_help if !@cmd or $cmd[0] =~ /^--?h/;
warn sprintf "\$ %s\n", join ' ', @cmd;
return system(@cmd) ? 0 : 1;
}
sub command_help {
my $script = shift;
print <<"HERE";
Usage: $0 COMMAND [OPTIONS]
Examples:
convos daemon
convos daemon --help
convos exec env | sort
convos exec prove -l
convos get /sw.js
convos get --help
convos install
convos install [--all|--dev|--prod]
convos version
For developers:
convos build
convos build release
convos cpanm
convos dev
convos dev --help
convos eval 'say app->core->home'
convos eval --help
Commands:
build Build JavaScript and CSS assets
cpanm Run cpanm and install to local lib
daemon Start the Convos server
dev Start the Convos development server
eval Run Perl code against Convos
exec Run shell command with Convos environment
get Perform HTTP request
install Install dependencies
upgrade Upgrade Convos to latest version
version Show versions of available modules
See also https://convos.chat/doc for more information.
HERE
}
sub command_install {
my ($script, $mode) = (@_, '--prod');
return $script->command_help if $mode =~ /^--?h/;
# @missing = ([mode, name, version, error], ...)
my @missing = grep { $_->[3] } $script->_dependencies;
@missing = grep { $_->[0] ne 'develop' } @missing if $mode =~ /^--?all/;
@missing = grep { $_->[0] eq 'requires' } @missing if $mode =~ /^--?prod/;
$script->command_cpanm(-n => $_->[1]) or die "cpanm failed: $?\n" for @missing;
$script->command_version;
print <<"HERE";
All dependencies are installed for "$mode".
You can now run "$0 daemon --listen http://*:8000" to start Convos,
or "$0 help" for more information.
HERE
}
sub command_upgrade {
my $script = shift;
my $home = path_to;
chdir $home or die "Couldn't change working directory to $home: $!";
if (-d '.git') {
my %branches = map { s![\s\r\n]+$!!; my $c = !!s!\*\s*!!; ($_ => $c) } qx(git branch);
my ($branch) = grep { $branches{$_} } keys %branches;
die qq(Couldn't find active git branch in "$home".\n) unless $branch;
$script->_run_or_abort(qw(git pull origin) => $branch);
}
else {
my $version = $ENV{CONVOS_WANTED_VERSION} || 'stable'; # experimental
my $url = "https://github.com/nordaaker/convos/archive/$version.tar.gz";
my $tar = _which('tar') or die qq(Couldn't find executable "tar".);
my $tar_cmd = "$tar xz --strip-components 1";
if (my $curl = _which('curl')) {
$script->_run_or_abort("$curl -s -L $url | $tar_cmd");
}
elsif (my $wget = _which('wget')) {
$script->_run_or_abort("$wget -q -O - $url | $tar_cmd");
}
else {
die qq(Couldn't find executable "curl" or "wget".);
}
}
return $script->command_install;
}
sub command_version {
my $script = shift;
my @deps
= map { $_->[3] ||= $_->[1]->VERSION; [$_->[0], sprintf " %-30s %-10s (%s)\n", @$_[1, 3, 2]] }
sort { $a->[1] cmp $b->[1] } $script->_dependencies;
unshift @deps, [requires => sprintf " %-30s %-10s (%s)\n", 'Perl', $^V, '5.16'];
unshift @deps, [requires => sprintf " %-30s %-10s (%s)\n", 'Convos', $VERSION, $VERSION];
my $dev_deps = join '', map { $_->[1] } grep { $_->[0] eq 'develop' } @deps;
my $req_deps = join '', map { $_->[1] } grep { $_->[0] eq 'requires' } @deps;
my $opt_deps = join '', map { $_->[1] } grep { $_->[0] eq 'suggests' } @deps;
print <<"HERE";
CORE
$req_deps
OPTIONAL
$opt_deps
DEVELOPMENT
$dev_deps
HERE
}
sub run {
my $script = shift;
return $script->command_help if !@_ or $_[0] =~ /^--?h/;
my $command = shift;
my $method = $script->can("command_$command");
$script->_setup_inc;
$script->_setup_env($command => @_);
$script->_exit($method => @_) if $method;
$script->_exit(1) unless $script->_dependencies_are_installed;
# Start Convos
require Mojolicious::Commands;
$script->_warn_running_as_root if +(!$< or !$>) and !$ENV{CONVOS_NO_ROOT_WARNING};
Mojolicious::Commands->start_app('Convos');
}
sub _cmd_with_secure_listen {
my ($script, @cmd) = @_;
require Mojo::URL;
my $url;
my $i = 0;
while ($i < @cmd) {
$url = Mojo::URL->new($cmd[$i]) if $cmd[$i] =~ m!^https?:!;
$i++;
}
$i = @cmd unless $url;
$url ||= Mojo::URL->new('https://*:3443');
return @cmd if $url->scheme eq 'http';
$url->query->param(cert => $ENV{CONVOS_TLS_CERT}) if $ENV{CONVOS_TLS_CERT};
$url->query->param(key => $ENV{CONVOS_TLS_KEY}) if $ENV{CONVOS_TLS_KEY};
return @cmd unless $url->query->param('cert') and $url->query->param('key');
splice @cmd, $i, 0, ($i == @cmd ? ('--listen') : ()), $url->to_string;
return @cmd;
}
sub _dependencies {
my $script = shift;
$script->_setup_inc;
my @dependencies;
my $load = sub {
my ($mode, $module, $version) = @_;
local ($@, $!) = ('', 0);
eval "use $module $version ();1";
my $err = $@;
$err =~ s! at .*!!s;
$err =~ s! in \@INC.*!!s;
$err =~ s!$module.*--.*?([\d\._]+).*!You have version $1!;
push @dependencies, [$script->{cpanfile_mode} || $mode, $module, $version, $err];
};
no warnings 'once';
local $script->{cpanfile_mode};
local *main::on = sub { local $script->{cpanfile_mode} = shift; shift->() };
local *main::requires = sub { $load->(requires => @_) };
local *main::suggests = sub { $load->(suggests => @_) };
local *main::test_requires = sub { $load->(test => @_) };
local ($@, $!) = ('', 0);
do $ENV{CONVOS_CPAN_FILE};
my $err = $@ || $!;
die "Could not source $ENV{CONVOS_CPAN_FILE}: $err\n" if $err;
return @dependencies;
}
sub _dependencies_are_installed {
my $script = shift;
return 1 if $ENV{CONVOS_SKIP_DEPENDENCIES_CHECK}++;
return 1 unless -e $ENV{CONVOS_CPAN_FILE};
return 1 unless grep { $_->[0] eq 'requires' and $_->[3] } $script->_dependencies;
$script->command_version;
print <<"HERE";
It is not possible to start Convos at this point, since there
are some missing dependencies that need to be installed.
Run "$0 install" to install the missing dependencies above.
HERE
return 0;
}
sub _exit {
my ($script, $method, @params) = @_;
exit $method if $method =~ m!^\d+$!;
$script->$method(@params) if $method;
exit 0 + ($? || $!);
}
sub _run_or_abort {
my ($script, @cmd) = @_;
return if $script->command_exec(@cmd);
my $exit_value = $? >> 8;
die "# Couldn't execute @cmd: $exit_value\n";
}
sub _setup_env {
my ($script, $command) = @_;
local ($@, $!) = ('', 0);
$ENV{CONVOS_CPAN_FILE} ||= path_to 'cpanfile';
$ENV{CONVOS_LOCAL_LIB} ||= path_to 'local';
$ENV{LINK_EMBEDDER_FORCE_SECURE} //= 1; # Make sure LinkEmbedder upgrade http to https
$ENV{MOJO_LOG_LEVEL} ||= 'fatal' if grep { $command eq $_ } qw(get version);
$ENV{MOJO_MODE} ||= 'production' if $command eq 'daemon';
$ENV{MOJO_NPM_BINARY} = 'pnpm';
$ENV{PERL5LIB} = join ':', @INC;
my $bin = path_to 'local', 'bin';
$ENV{PATH} = join ':', grep {$_} $bin, $ENV{PATH} if -d $bin;
opendir my $DH, path_to;
while ($DH and (my $file = readdir $DH)) {
my $file = path_to $file;
$ENV{CONVOS_TLS_KEY} = $file and next if $file =~ m!-key\.pem$!;
$ENV{CONVOS_TLS_CERT} = $file and next if $file =~ m!\.pem$!;
}
}
sub _setup_inc {
my $script = shift;
local ($@, $!) = ('', 0);
# Where cpanm might have installed dependencies to
unshift @INC,
grep {-d} map { path_to 'local', 'lib', 'perl5', @$_ }[$Config{version}, $Config{archname}],
[$Config{version}], [$Config{archname}], [];
# Where Convos lives
unshift @INC, path_to 'lib';
# Force PERL5LIB to be loaded before the custom @INC directories above
unshift @INC, split /:/, $ENV{PERL5LIB} if $ENV{PERL5LIB};
my %uniq;
@INC = grep { !$uniq{$_}++ } @INC; # duplicates are caused by "dev" command
pop @INC if $INC[-1] eq '.'; # don't care about current dir
}
sub _warn_running_as_root {
print <<"HERE";
UID = $<
EUID = $>
USER = $ENV{USER}
--------------------------------------------------------------------
WARNING!
--------------------------------------------------------------------
You should NOT run Convos as root!
It is not considered a good security practice to run servers as the
root user.
Note that if you used to run Convos as root, then you have to change
ownership to files in your "\$CONVOS_HOME" directory.
We strongly encourage you to change to a less privileged user.
--------------------------------------------------------------------
HERE
}
sub _which {
my $name = shift;
for my $dir (split ':', $ENV{PATH}) {
my $path = File::Spec->catfile($dir, $name);
return $path if -x $path;
}
return undef;
}
You can’t perform that action at this time.