Small, simple script to deploy small simple websites, without real packaging. Really, it's a bit silly. But useful.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

403 lines
11 KiB

  1. #!/usr/bin/env perl
  2. ## --------------------------------------------------------------------------------------------------------------------
  3. use Modern::Perl;
  4. use Config::Simple;
  5. use File::Slurp;
  6. use File::Temp ();
  7. use IPC::Run3;
  8. use Cwd qw();
  9. use File::Basename qw();
  10. use JSON::Any;
  11. ## --------------------------------------------------------------------------------------------------------------------
  12. title("The Deployer is Deploying - Stand Back!");
  13. # check we can get sudo
  14. run("sudo echo");
  15. ## --------------------------------------------------------------------------------------------------------------------
  16. # Always update the code first, since `deployer/settings` or `deployer/env` need to be read after an update.
  17. sep();
  18. title("Updating the Code");
  19. run('git fetch --verbose');
  20. run('git rebase');
  21. ## --------------------------------------------------------------------------------------------------------------------
  22. # Setup
  23. sep();
  24. title("Running Setup");
  25. # You wouldn't do this normally since it depends on an ENV var, instead use User or File::HomeDir,
  26. my $username = $ENV{USER};
  27. my $dir = Cwd::cwd();
  28. my ($name) = File::Basename::fileparse($dir);
  29. my $safe_name = $name;
  30. $safe_name =~ s/\./-/g;
  31. my $is_node = 0;
  32. my $is_golang = 0;
  33. my $is_nebulous = 0;
  34. if ( -f 'package.json' || -f 'package-lock.json' ) {
  35. my $start = `jq -r '.dependencies."nebulous-server"' package.json`;
  36. chomp $start;
  37. if ( defined $start && $start ne 'null' ) {
  38. $is_nebulous = 1;
  39. }
  40. else {
  41. $is_node = 1;
  42. }
  43. }
  44. if ( -f 'vendor/manifest' ) {
  45. $is_golang = 1;
  46. }
  47. my $is_nginx_done = 0;
  48. my $setting = {};
  49. my $settings = new Config::Simple('deployer/settings');
  50. if ( defined $settings ) {
  51. %$setting = $settings->vars();
  52. }
  53. my $apex = $setting->{apex};
  54. my $port = $setting->{port};
  55. my $www = defined $setting->{www} ? ($setting->{www}+0) : 1; # default: add the `www.$apex` server
  56. my $cmd = $setting->{cmd};
  57. my $env = {};
  58. if ( -f 'deployer/env' ) {
  59. my $cfg = new Config::Simple('deployer/env');
  60. if ( defined $cfg ) {
  61. %$env = $cfg->vars();
  62. }
  63. }
  64. msg("User : $ENV{USER}");
  65. msg("Current Dir : $dir");
  66. msg("Name : $name");
  67. msg("Safe Name : $safe_name");
  68. msg("Is Node.js? : $is_node");
  69. msg("Is GoLang? : $is_golang");
  70. msg("Is Nebulous? : $is_nebulous");
  71. msg("Settings :");
  72. msg(" - apex=$apex");
  73. msg(" - port=$port");
  74. msg(" - www=$www");
  75. msg(" - cmd=" . ($cmd || ''));
  76. msg("Env :");
  77. while (my ($k, $v) = each(%$env)) {
  78. msg(" - $k=$v");
  79. if ($v eq "?") {
  80. # firstly, see if a `deployer/ENV_$k` file exists
  81. my $filename = "deployer/ENV_$k";
  82. if ( -f $filename ) {
  83. my $value = read_file($filename);
  84. chomp $value;
  85. $env->{$k} = $value;
  86. }
  87. else {
  88. print " Value? - $k=";
  89. my $value = <STDIN>;
  90. chomp $value;
  91. $env->{$k} = $value;
  92. write_file($filename, $value);
  93. }
  94. msg(" - $k=$env->{$k}");
  95. }
  96. }
  97. ## --------------------------------------------------------------------------------------------------------------------
  98. # Packages
  99. sep();
  100. title("Checking Packages");
  101. if ( -f 'deployer/packages' ) {
  102. my @pkgs = read_file('deployer/packages');
  103. chomp @pkgs;
  104. for my $pkg ( @pkgs ) {
  105. run("dpkg-query --show $pkg");
  106. }
  107. }
  108. else {
  109. msg("No 'packages' file.");
  110. }
  111. ## --------------------------------------------------------------------------------------------------------------------
  112. # Update Packages
  113. # if ( $is_node ) {
  114. # sep();
  115. # title("Installing NPM Packages");
  116. # run('npm ci');
  117. # run('npm run build');
  118. # run('npm ci --production');
  119. # }
  120. if ( $is_golang ) {
  121. sep();
  122. title("Building GoLang");
  123. run('gb build');
  124. }
  125. if ( $is_nebulous ) {
  126. sep();
  127. title("Installing NPM Packages");
  128. run('npm ci');
  129. }
  130. ## --------------------------------------------------------------------------------------------------------------------
  131. # Make
  132. sep();
  133. title("Making the Project");
  134. if ( -f "Makefile" ) {
  135. run("make");
  136. }
  137. else {
  138. msg("No 'Makefile'.");
  139. }
  140. ## --------------------------------------------------------------------------------------------------------------------
  141. # Minify
  142. sep();
  143. title("Minifying Assets");
  144. if ( -f "deployer/minify" ) {
  145. my @minifies = read_file('deployer/minify');
  146. chomp @minifies;
  147. for my $minify ( @minifies ) {
  148. my ($type, $filename) = split(':', $minify);
  149. if ( $type eq 'css' ) {
  150. msg("Minifying CSS : $filename.css");
  151. run("curl -X POST -s --data-urlencode 'input\@$filename.css' https://cssminifier.com/raw > $filename.min.css");
  152. }
  153. if ( $type eq 'js' ) {
  154. msg("Minifying JavaScript : $filename.js");
  155. run("curl -X POST -s --data-urlencode 'input\@$filename.js' https://javascript-minifier.com/raw > $filename.min.js");
  156. }
  157. if ( $type eq 'png' ) {
  158. msg("Crushing PNG : $filename.png");
  159. run("curl -X POST -s --form 'input=\@filename.png;type=image/png' https://pngcrush.com/crush > $filename.min.png");
  160. }
  161. if ( $type eq 'jpg' ) {
  162. msg("Optimising JPG : $filename.jpg");
  163. run("curl -X POST -s --form 'input=\@filename.jpg;type=image/jpg' https://jpgoptimiser.com/optimise > $filename.min.jpg");
  164. }
  165. }
  166. }
  167. else {
  168. msg("No 'minify' file.");
  169. }
  170. ## --------------------------------------------------------------------------------------------------------------------
  171. # Dirs
  172. sep();
  173. title("Creating Dirs");
  174. # for supervisord logging
  175. run("sudo mkdir -p /var/log/$name/");
  176. my @dirs = read_file('deployer/dirs');
  177. chomp @dirs;
  178. for my $line ( @dirs ) {
  179. run("sudo mkdir -p $line");
  180. run("sudo chown $username.$username $line");
  181. }
  182. ## --------------------------------------------------------------------------------------------------------------------
  183. # Cron
  184. sep();
  185. title("Cron");
  186. if ( -f "deployer/cron.d" ) {
  187. run("sudo cp deployer/cron.d /etc/cron.d/$safe_name");
  188. }
  189. else {
  190. msg("No cron found");
  191. }
  192. ## --------------------------------------------------------------------------------------------------------------------
  193. # Supervisor
  194. sep();
  195. title("Supervisor");
  196. # create each line of the supervisor file
  197. my @supervisor;
  198. # create each line
  199. push(@supervisor, "[program:$safe_name]\n");
  200. push(@supervisor, "directory = $dir\n");
  201. if ( $cmd ) {
  202. push(@supervisor, "command = $cmd\n");
  203. }
  204. elsif ( $is_node ) {
  205. push(@supervisor, "command = node server.js\n");
  206. }
  207. elsif ( $is_nebulous ) {
  208. push(@supervisor, "command = npm start\n");
  209. }
  210. else {
  211. push(@supervisor, "command = echo 'Error: Unknown deployer command.'\n");
  212. }
  213. push(@supervisor, "user = $username\n");
  214. push(@supervisor, "autostart = true\n");
  215. push(@supervisor, "autorestart = true\n");
  216. push(@supervisor, "start_retries = 3\n");
  217. push(@supervisor, "stdout_logfile = /var/log/$name/stdout.log\n");
  218. push(@supervisor, "stdout_logfile_maxbytes = 50MB\n");
  219. push(@supervisor, "stdout_logfile_backups = 20\n");
  220. push(@supervisor, "stderr_logfile = /var/log/$name/stderr.log\n");
  221. push(@supervisor, "stderr_logfile_maxbytes = 50MB\n");
  222. push(@supervisor, "stderr_logfile_backups = 20\n");
  223. # environment
  224. push(@supervisor, "environment = APEX=\"$apex\",PORT=\"$port\"");
  225. if ( $is_node || $is_nebulous ) {
  226. push(@supervisor, ",NODE_ENV=\"production\"");
  227. }
  228. # copy all ENV VARS over
  229. while (my ($k, $v) = each(%$env)) {
  230. push(@supervisor, ",$k=\"$v\"");
  231. }
  232. push(@supervisor, "\n");
  233. # write this out to a file
  234. my $supervisor_fh = File::Temp->new();
  235. my $supervisor_filename = $supervisor_fh->filename;
  236. msg("Writing $supervisor_filename");
  237. msg(@supervisor);
  238. write_file($supervisor_fh, @supervisor);
  239. run("sudo cp $supervisor_filename /etc/supervisor/conf.d/$name.conf");
  240. run("sudo supervisorctl restart $safe_name");
  241. ## --------------------------------------------------------------------------------------------------------------------
  242. # Nginx
  243. sep();
  244. title("Nginx");
  245. # Skip if the Nginx config already exists.
  246. if ( ! -f "/etc/nginx/sites-available/$name.conf" ) {
  247. my @nginx;
  248. push(@nginx, "server {\n");
  249. push(@nginx, " listen 80;\n");
  250. push(@nginx, " server_name $apex;\n");
  251. push(@nginx, " location / {\n");
  252. push(@nginx, " proxy_set_header X-Real-IP \$remote_addr;\n");
  253. push(@nginx, " proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;\n");
  254. # chilts@zool:~$ sudo nginx -t
  255. # nginx: [emerg] unknown "proxy_x_forwarded_proto" variable
  256. # nginx: configuration file /etc/nginx/nginx.conf test failed
  257. # push(@nginx, " proxy_set_header X-Forwarded-Proto \$proxy_x_forwarded_proto;\n");
  258. push(@nginx, " proxy_set_header Host \$http_host;\n");
  259. push(@nginx, " proxy_pass http://localhost:$port;\n");
  260. push(@nginx, " }\n");
  261. push(@nginx, "}\n");
  262. push(@nginx, "\n");
  263. if ( $www ) {
  264. push(@nginx, "server {\n");
  265. push(@nginx, " listen 80;\n");
  266. push(@nginx, " server_name www.$apex;\n");
  267. push(@nginx, " return 301 \$scheme://$apex\$request_uri;\n");
  268. push(@nginx, "}\n");
  269. }
  270. # write this out to a file
  271. my $nginx_fh = File::Temp->new();
  272. my $nginx_filename = $nginx_fh->filename;
  273. msg("Writing $nginx_filename");
  274. msg(@nginx);
  275. write_file($nginx_fh, @nginx);
  276. run("sudo cp $nginx_filename /etc/nginx/sites-available/$apex.conf");
  277. # only do the symlink if it doesn't already exist
  278. if ( ! -l "/etc/nginx/sites-enabled/$apex.conf" ) {
  279. run("sudo ln -s /etc/nginx/sites-available/$apex.conf /etc/nginx/sites-enabled/$apex.conf");
  280. }
  281. run("sudo service nginx restart");
  282. }
  283. else {
  284. $is_nginx_done = 1;
  285. msg("Nginx config already set up. You'll need to make changes manually to force any changes.");
  286. }
  287. ## --------------------------------------------------------------------------------------------------------------------
  288. # CertBot
  289. sep();
  290. title("CertBot");
  291. if ( $is_nginx_done ) {
  292. msg("Since nginx was set up previously, you don't need to run");
  293. msg("certbot now if you have already set up a certificate.");
  294. msg("");
  295. msg("If this message is incorrect, you may run it with:");
  296. }
  297. else {
  298. msg("To tell CertBot about this new Nginx config, you can run:");
  299. }
  300. msg("");
  301. msg("\$ sudo certbot --nginx");
  302. ## --------------------------------------------------------------------------------------------------------------------
  303. sep();
  304. title("Complete!");
  305. ## --------------------------------------------------------------------------------------------------------------------
  306. sub title {
  307. my ($msg) = @_;
  308. print "-----> $msg\n";
  309. }
  310. sub msg {
  311. my (@msg) = @_;
  312. chomp @msg;
  313. for my $line ( @msg ) {
  314. print " $line\n";
  315. }
  316. }
  317. sub err {
  318. my (@msg) = @_;
  319. chomp @msg;
  320. for my $line ( @msg ) {
  321. print "Error: $line\n";
  322. }
  323. }
  324. sub sep {
  325. print "\n";
  326. }
  327. sub run {
  328. my ($cmd) = @_;
  329. my @stdin;
  330. my @stdout;
  331. my @stderr;
  332. msg("\$ $cmd");
  333. run3($cmd, \undef, \@stdout, \@stderr);
  334. if ( $? ) {
  335. err(@stderr);
  336. exit $?;
  337. }
  338. msg(@stdout);
  339. }
  340. ## --------------------------------------------------------------------------------------------------------------------