├── .envrc-sample ├── .gitmodules ├── LICENSE ├── README.md ├── bookworm-deb-boot.sh ├── bootstrap-debian-bullseye.sh ├── bootstrap-debian-buster.sh ├── bootstrap-ubuntu-focal.sh ├── bootstrap-ubuntu-jammy.sh ├── bootstrap-ubuntu-noble.sh ├── bootstrap.sh └── scripts ├── awscli-install-update-script.sh ├── base-installation.sh ├── bootstrap-root.sh ├── bootstrap-server-admin.sh ├── bootstrap-wp-user.sh ├── cron-tweaks.sh ├── deny-root-login.sh ├── email-mta-installation.sh ├── firewall.sh ├── hostname.sh ├── linux-tweaks.sh ├── lowendtalk.sh ├── memcached-install.bash ├── mysql-installation.sh ├── nginx-installation.sh ├── nvm-nodejs.sh ├── optional-installation.sh ├── php-installation.sh ├── pma-installation.sh ├── pma-user-creation.sh ├── post-install-bionic.sh ├── post-install-buster.sh ├── post-install-stretch.sh ├── post-install-xenial.sh ├── redis.sh ├── restrict-wp-user-to-sftp.sh ├── server-admin-creation.sh ├── swap.sh ├── test-env-creation.sh ├── unattended-reboots.sh ├── unattended-upgrades.sh ├── wp-cli-install.bash └── wp-user-creation.sh /.envrc-sample: -------------------------------------------------------------------------------- 1 | # instructions 2 | # ============ 3 | 4 | # fill the values 5 | # copy or rename it to .envrc 6 | # place it at $HOME of root, that is /root/.envrc 7 | 8 | # this user can be used to access the server via SSH 9 | # if empty, a random username will be created 10 | export ADMIN_USERNAME= 11 | export system_admin_username=$ADMIN_USERNAME # for backward compatibility 12 | 13 | # WP user is provided with SFTP access (at least) 14 | # if empty, a random username will be created 15 | export WP_USERNAME= 16 | export WP_PASSWORD= 17 | export DEV_USER=$WP_USERNAME # for backward compatibility 18 | export web_developer_username=$DEV_USER # for backward compatibility 19 | 20 | # These are used in multiple places such as crontab, logwatch, git 21 | #export EMAIL= 22 | #export NAME= 23 | 24 | # To differentiate various environments... local, dev, staging. 25 | # Default: production. 26 | # ENV_TYPE= 27 | 28 | export ADMIN_EMAIL= 29 | export CERTBOT_ADMIN_EMAIL= 30 | export CLIENT_EMAIL= 31 | 32 | # End of predefined values 33 | 34 | # The following are generated automatically by wp-in-a-box bootstrap script... 35 | 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | # how to update submodules 2 | # git submodule update --remote --merge 3 | # git commit -am 'update submodule to the latest version' 4 | # git push origin main 5 | # 6 | # how to initialize and update submodules 7 | # https://stackoverflow.com/q/5828324/1004587 8 | # git submodule update --init 9 | # git submodule update --remote 10 | # git commit -am 'update submodule to the latest version' 11 | # git push origin BRANCH 12 | 13 | # Get the submodule initially 14 | # git submodule add https://github.com/user/repo submodule_dir 15 | # git submodule init 16 | [submodule "snippets"] 17 | path = snippets 18 | url = https://github.com/pothi/snippets 19 | branch = main 20 | [submodule "config"] 21 | path = config 22 | url = https://github.com/pothi/linux-tweaks-deb 23 | branch = main 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP In A Box 2 | 3 | Script/s to install LEMP in a linux box. This LEMP stack is fine-tuned towards WordPress installations. It may work for other PHP based applications, too. For more details, please see the blog post at [https://www.tinywp.in/wp-in-a-box/](https://www.tinywp.in/wp-in-a-box/). 4 | 5 | There are a number of similar scripts available on the internet. The unique feature of this repo is in [security considerations](https://github.com/pothi/wp-in-a-box#security-considerations). 6 | 7 | ## Supported Platforms 8 | 9 | Ubuntu 10 | 11 | + [Ubuntu Noble Numbat (24.04.x)](https://github.com/pothi/wp-in-a-box/blob/main/bootstrap-ubuntu-noble.sh) 12 | + [Ubuntu Jammy Jellyfish (22.04.x)](https://github.com/pothi/wp-in-a-box/blob/main/bootstrap-ubuntu-jammy.sh) 13 | + [Ubuntu Focal Fossa (20.04.x)](https://github.com/pothi/wp-in-a-box/blob/main/bootstrap-ubuntu-focal.sh) 14 | + Ubuntu Bionic Beaver (18.04.x) 15 | + Ubuntu Xenial Xerus (16.04.x) 16 | 17 | Debian 18 | 19 | + [Debian Bookworm (12.x)](https://github.com/pothi/wp-in-a-box/blob/main/bookworm-deb-boot.sh) 20 | + [Debian Bullseye (11.x)](https://github.com/pothi/wp-in-a-box/blob/main/bootstrap-debian-bullseye.sh) 21 | + [Debian Buster (10.x)](https://github.com/pothi/wp-in-a-box/blob/main/bootstrap-debian-buster.sh) 22 | + Debian Stretch (9.x) 23 | 24 | ## Generic Goals 25 | 26 | In sync with WordPress philosophy of “[decision, not options](https://wordpress.org/about/philosophy/)”. 27 | 28 | ## Performance Considerations 29 | 30 | - No added bloatware 31 | - Redis for object cache (available as an optional package) 32 | - Full page cache support (WP Super Cache, WP Rocket and WP Fastest Cache) 33 | - PHP 8 (or lower when necessary) 34 | - Nginx (no Apache, sorry) 35 | - Varnish (planned, but no ETA) 36 | - Swap 37 | 38 | ## Security Considerations 39 | 40 | - No phoning home. 41 | - No external dependencies (such as third-party repositories, unless there is a strong reason to use it). 42 | - Automatic security updates (with an option to update everything). 43 | - Disable password authentication for root. 44 | - Nginx (possibly with Naxsi WAF when h2 issue is resolved). 45 | - Umask 027 or 077. 46 | - ACL integration. 47 | - Weekly logwatch (if email is supplied). 48 | - Isolated user for PhpMyAdmin. 49 | - PHP user and Nginx user run under different username. 50 | - Only ports 80, 443, and port for SSH are open. 51 | 52 | ## Implementation Details 53 | 54 | - Agentless. 55 | - Idempotent. 56 | - Random username (like GoDaddy generates). 57 | - Automatic restart of MySQL (and Varnish) upon failure. 58 | - Integrated wp-cli. 59 | - Support for version control (git, hg). 60 | - Composer pre-installed. 61 | - Auto-update of almost everything (wp-cli, composer, certbot certs, etc). 62 | 63 | ## Roadmap 64 | 65 | - Automatic Certbot / LetsEncrypt installation and renewal (like Caddy). 66 | - Automated setup of sites using backups. 67 | - Web interface (planned, but no ETA). 68 | - Automatic backup of site/s (files and DB) to AWS S3 or to GCP. 69 | 70 | ## Install procedure 71 | 72 | - Rename `.envrc-sample` file as `.envrc` and insert as much information as possible 73 | - Download `bootstrap.sh` and execute it. 74 | 75 | ```bash 76 | # as root 77 | 78 | apt install curl screen -y 79 | 80 | # optional steps 81 | # curl -LO https://github.com/pothi/wp-in-a-box/raw/main/.envrc-sample 82 | # cp .envrc-sample .envrc 83 | # nano .envrc 84 | 85 | # download the bootstrap script 86 | curl -LO https://github.com/pothi/wp-in-a-box/raw/main/bootstrap.sh 87 | 88 | # please do not trust any script on the internet or github 89 | # so, please go through it! 90 | nano ~/bootstrap.sh 91 | 92 | # execute it and wait for some time 93 | # screen bash bootstrap.sh 94 | # or simply 95 | bash bootstrap.sh 96 | 97 | # wait for the installation to get over. 98 | # it can take approximately 5 minutes on a 2GB server 99 | # it depends on CPU power too 100 | 101 | # we no longer needs bootstrap.sh file 102 | rm bootstrap.sh 103 | 104 | # to see the credentials to log in to the server from now 105 | # this is the important step. you can't login as root from now on 106 | cat ~/.envrc 107 | 108 | ``` 109 | 110 | ## What you get at the end of the installation 111 | 112 | You may find the following details at `~/.envrc` file... 113 | 114 | - a SSH user (prefixed with `ssh_`) with sudo privileges (use it only to manage the server such as to create a new MySQL database or to create a new vhost entry for Nginx) 115 | - a chrooted SFTP user, prefixed with `sftp_web_`, with its home directory at `/home/web` along with some common directories(such as `~/log`, `~/sites`, etc) created already. (you may give it to your developer to access the file system such as to upload a new theme, etc) 116 | - a dedicated MySQL username (and password) with all the privileges as `root` user. This can be used to access MySQL via PhpMyAdmin, as `root` user can only access MySQL via cli. 117 | 118 | ## Where to install WordPress & How to install it 119 | 120 | - PHP runs as SFTP user. So, please install WordPress **as** SFTP user at `/home/web/sites/example.com/public`. 121 | - Configure Nginx using pre-defined templates that can be found at the companion repo [WordPress-Nginx](https://github.com/pothi/wordpress-nginx). That repo is already installed. You just have to copy / paste one of [the templates](https://github.com/pothi/wordpress-nginx/tree/main/sites-available) to fit your domain name. 122 | - If you wish to deploy SSL, a [Let's Encrypt](https://letsencrypt.org/) client is already installed. Please use the command `certbot certonly --webroot -w /home/web/sites/example.com/public -d example.com -d www.example.com`. The renewal script is already in place as a cron entry. So, you don't have to create a new entry. To know more about this client library and to know more about the available options, please visit [https://certbot.eff.org/](https://certbot.eff.org/) . 123 | 124 | ## Known Limitations 125 | 126 | - SFTP user can not create or upload new files and folders at `$HOME`, but can create or upload inside other existing directories. This is [a known limitation](https://wiki.archlinux.org/index.php/SFTP_chroot#Write_permissions) when we use SFTP capability of built-in OpenSSH server. 127 | 128 | ## Wiki 129 | 130 | For more documentation, information, supported/tested hosts, todo, etc, please see the [WP-In-A-Box wiki](https://github.com/pothi/wp-in-a-box/wiki). 131 | -------------------------------------------------------------------------------- /bookworm-deb-boot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # Version: 1.0 7 | 8 | # this is the PHP version that comes by default with the current Ubuntu LTS 9 | php_ver=8.2 10 | 11 | # to be run as root, probably as a user-script just after a server is installed 12 | # https://stackoverflow.com/a/52586842/1004587 13 | # also see https://stackoverflow.com/q/3522341/1004587 14 | is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; } 15 | [ is_user_root ] || { echo 'You must be root or have sudo privilege to run this script. Exiting now.'; exit 1; } 16 | 17 | export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin 18 | export DEBIAN_FRONTEND=noninteractive 19 | 20 | [ -d ~/backups ] || mkdir ~/backups 21 | [ -d ~/log ] || mkdir ~/log 22 | 23 | # logging everything 24 | log_file=${HOME}/log/wp-in-a-box.log 25 | exec > >(tee -a ${log_file} ) 26 | exec 2> >(tee -a ${log_file} >&2) 27 | 28 | echo "Script started on (date & time): $(date +%c)" 29 | 30 | #--- Useful functions ---# 31 | # helper function to exit upon non-zero exit code of a command 32 | # usage some_command; check_result $? 'some_command failed' 33 | if ! $(type 'check_result' 2>/dev/null | grep -q 'function') ; then 34 | check_result() { 35 | if [ "$1" -ne 0 ]; then 36 | echo -e "\nError: $2. Exiting!\n" 37 | exit "$1" 38 | fi 39 | } 40 | fi 41 | 42 | # function to configure timezone to UTC 43 | set_utc_timezone() { 44 | if [ "$(date +\%Z)" != "UTC" ] ; then 45 | [ ! -f /usr/sbin/tzconfig ] && apt-get -qq install tzdata > /dev/null 46 | printf '%-72s' "Setting up timezone..." 47 | ln -fs /usr/share/zoneinfo/UTC /etc/localtime 48 | dpkg-reconfigure -f noninteractive tzdata 49 | # timedatectl set-timezone UTC 50 | check_result $? 'Error setting up timezone.' 51 | 52 | # Recommended to restart cron after every change in timezone 53 | systemctl restart cron 54 | check_result $? 'Error restarting cron daemon after changing timezone.' 55 | echo done. 56 | fi 57 | } 58 | 59 | if ! $(type 'install_package' 2>/dev/null | grep -q 'function') ; then 60 | install_package() { 61 | package=$1 62 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 63 | then 64 | # echo "'$package' is already installed" 65 | : 66 | else 67 | printf '%-72s' "Installing '${package}' ..." 68 | apt-get -qq install $package > /dev/null 69 | check_result $? "Couldn't install $package." 70 | echo done. 71 | fi 72 | } 73 | fi 74 | 75 | #--- end of Useful functions ---# 76 | 77 | # if ~/.envrc doesn't exist, create it 78 | if [ ! -f "$HOME/.envrc" ]; then 79 | touch ~/.envrc 80 | chmod 600 ~/.envrc 81 | # if exists, source it to apply the env variables 82 | else 83 | . ~/.envrc 84 | fi 85 | 86 | [ -z "$PHP_VERSION" ] && echo "export PHP_VERSION=$php_ver" >> /root/.envrc 87 | 88 | #--- swap ---# 89 | if free | awk '/^Swap:/ {exit $2}'; then 90 | printf '%-72s' "Creating swap..." 91 | wget -O /tmp/swap.sh -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/swap.sh 92 | bash /tmp/swap.sh >/dev/null 93 | rm /tmp/swap.sh 94 | echo done. 95 | fi 96 | 97 | #--- apt tweaks ---# 98 | 99 | # Ref: https://wiki.debian.org/Multiarch/HOWTO 100 | # https://askubuntu.com/a/1336013/65814 101 | [ ! $(dpkg --get-selections | grep -q i386) ] && dpkg --remove-architecture i386 2>/dev/null 102 | 103 | # Fix apt ipv4/6 issue 104 | [ ! -f /etc/apt/apt.conf.d/1000-force-ipv4-transport ] && \ 105 | echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/1000-force-ipv4-transport 106 | 107 | # Fix a warning related to dialog 108 | # run `debconf-show debconf` to see the current /default selections. 109 | echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 110 | 111 | # the following runs when apt cache is older than 6 hours 112 | # Taken from Ansible - https://askubuntu.com/a/1362550/65814 113 | APT_UPDATE_SUCCESS_STAMP_PATH=/var/lib/apt/periodic/update-success-stamp 114 | APT_LISTS_PATH=/var/lib/apt/lists 115 | if [ -f "$APT_UPDATE_SUCCESS_STAMP_PATH" ]; then 116 | if [ -z "$(find "$APT_UPDATE_SUCCESS_STAMP_PATH" -mmin -360 2> /dev/null)" ]; then 117 | printf '%-72s' "Updating apt cache" 118 | apt-get -qq update 119 | echo done. 120 | fi 121 | elif [ -d "$APT_LISTS_PATH" ]; then 122 | if [ -z "$(find "$APT_LISTS_PATH" -mmin -360 2> /dev/null)" ]; then 123 | printf '%-72s' "Updating apt cache" 124 | apt-get -qq update 125 | echo done. 126 | fi 127 | fi 128 | 129 | # ref: https://askubuntu.com/q/114759/65814 (use any one solution - the accepted answer and the other) 130 | # ref: https://www.server-world.info/en/note?os=Debian_10&p=locale - once you installed locales-all, you can not use the accepted solution from askubuntu. 131 | lang=$LANG 132 | if [ "$lang" != "en_US.UTF-8" ]; then 133 | install_package locales 134 | 135 | # localectl set-locale LANG=en_US.UTF-8 136 | locale-gen en_US.UTF-8 >/dev/null 137 | update-locale LANG=en_US.UTF-8 138 | source /etc/default/locale 139 | fi 140 | 141 | # -------------------------- Prerequisites ------------------------------------ 142 | 143 | # apt-utils to fix an annoying non-critical bug on minimal images. Ref: https://github.com/tianon/docker-brew-ubuntu-core/issues/59 144 | install_package apt-utils 145 | 146 | required_packages="apt-transport-https \ 147 | curl \ 148 | dnsutils \ 149 | fail2ban \ 150 | git \ 151 | memcached \ 152 | python3-venv \ 153 | snapd \ 154 | software-properties-common \ 155 | sudo \ 156 | unzip \ 157 | wget" 158 | 159 | for package in $required_packages 160 | do 161 | install_package $package 162 | done 163 | 164 | # MySQL is required by PHP. 165 | install_package default-mysql-server 166 | 167 | # PHP is required by Nginx to configure the defaults. 168 | php_packages="php${php_ver}-common \ 169 | php${php_ver}-mysql \ 170 | php${php_ver}-gd \ 171 | php${php_ver}-cli \ 172 | php${php_ver}-xml \ 173 | php${php_ver}-mbstring \ 174 | php${php_ver}-soap \ 175 | php${php_ver}-curl \ 176 | php${php_ver}-zip \ 177 | php${php_ver}-bcmath \ 178 | php${php_ver}-intl \ 179 | php${php_ver}-imagick \ 180 | php${php_ver}-memcache \ 181 | php${php_ver}-memcached \ 182 | php${php_ver}-fpm" 183 | 184 | # package=php${php_ver}-fpm 185 | for package in $php_packages 186 | do 187 | install_package $package 188 | done 189 | 190 | # nginx 191 | install_package nginx-extras 192 | 193 | # fail2ban needs to be started manually after installation. 194 | systemctl start fail2ban 195 | 196 | # configure some defaults for git and etckeeper 197 | git config --global user.name "root" 198 | git config --global user.email "root@localhost" 199 | git config --global init.defaultBranch main 200 | 201 | #--- setup timezone ---# 202 | set_utc_timezone 203 | 204 | # initial backup of /etc 205 | [ -d ~/backup/etc-init ] || cp -a /etc ~/backups/etc-init 206 | 207 | # Create a WordPress user with /home/web as $HOME 208 | wp_user=${WP_USERNAME:-""} 209 | if [ "$wp_user" == "" ]; then 210 | printf '%-72s' "Creating a WP User..." 211 | wp_user="wp_$(openssl rand -base64 32 | tr -d /=+ | cut -c -10)" 212 | echo "export WP_USERNAME=$wp_user" >> /root/.envrc 213 | echo done. 214 | fi 215 | 216 | home_basename=$(echo $wp_user | awk -F _ '{print $1}') 217 | [ -z $home_basename ] && home_basename=web 218 | 219 | useradd --shell=/bin/bash -m --home-dir /home/${home_basename} $wp_user 220 | chmod 755 /home/$home_basename 221 | 222 | groupadd ${home_basename} 223 | gpasswd -a $wp_user ${home_basename} > /dev/null 224 | 225 | # Create password for WP User 226 | wp_pass=${WP_PASSWORD:-""} 227 | if [ "$wp_pass" == "" ]; then 228 | printf '%-72s' "Creating password for WP user..." 229 | wp_pass=$(openssl rand -base64 32 | tr -d /=+ | cut -c -20) 230 | echo "export WP_PASSWORD=$wp_pass" >> /root/.envrc 231 | echo done. 232 | fi 233 | 234 | echo "$wp_user:$wp_pass" | chpasswd 235 | 236 | # provide sudo access without passwd to WP User 237 | if [ ! -f /etc/sudoers.d/$wp_user ]; then 238 | printf '%-72s' "Providing sudo privilege for WP user..." 239 | echo "${wp_user} ALL=(ALL) NOPASSWD:ALL"> /etc/sudoers.d/$wp_user 240 | chmod 400 /etc/sudoers.d/$wp_user 241 | echo done. 242 | fi 243 | 244 | # Enable password authentication for WP User 245 | cd /etc/ssh/sshd_config.d 246 | if [ ! -f enable-passwd-auth.conf ]; then 247 | printf '%-72s' "Enabling Password Authentication for WP user..." 248 | echo "PasswordAuthentication yes" > enable-passwd-auth.conf 249 | /usr/sbin/sshd -t && systemctl reload sshd 250 | check_result $? 'Error restarting SSH daemon while enabling passwd auth.' 251 | echo done. 252 | fi 253 | cd - >/dev/null 254 | 255 | echo ---------------------------------- LEMP ------------------------------------- 256 | 257 | # ------------------------------- MySQL --------------------------------------- 258 | 259 | # Create a MySQL admin user 260 | sql_user=${MYSQL_ADMIN_USER:-""} 261 | if [ "$sql_user" == "" ]; then 262 | printf '%-72s' "Creating a MySQL Admin User..." 263 | # create MYSQL username automatically 264 | # unique username / password generator: https://unix.stackexchange.com/q/230673/20241 265 | sql_user="mysql_$(openssl rand -base64 32 | tr -d /=+ | cut -c -10)" 266 | echo "export MYSQL_ADMIN_USER=$sql_user" >> /root/.envrc 267 | echo done. 268 | fi 269 | 270 | sql_pass=${MYSQL_ADMIN_PASS:-""} 271 | if [ "$sql_pass" == "" ]; then 272 | sql_pass=$(openssl rand -base64 32 | tr -d /=+ | cut -c -20) 273 | echo "export MYSQL_ADMIN_PASS=$sql_pass" >> /root/.envrc 274 | fi 275 | 276 | mysql -e "CREATE USER IF NOT EXISTS ${sql_user} IDENTIFIED BY '${sql_pass}';" 277 | mysql -e "GRANT ALL PRIVILEGES ON *.* TO ${sql_user} WITH GRANT OPTION" 278 | 279 | # echo -------------------------------- PHP ---------------------------------------- 280 | echo -------------------------------- Configuring PHP ---------------------------------------- 281 | 282 | php_user=$wp_user 283 | fpm_ini_file=/etc/php/${php_ver}/fpm/php.ini 284 | pool_file=/etc/php/${php_ver}/fpm/pool.d/${php_user}.conf 285 | default_pool_file=/etc/php/${php_ver}/fpm/pool.d/www.conf 286 | PM_METHOD=ondemand 287 | 288 | user_mem_limit=${PHP_MEM_LIMIT:-""} 289 | [ -z "$user_mem_limit" ] && user_mem_limit=512 290 | 291 | max_children=${PHP_MAX_CHILDREN:-""} 292 | 293 | if [ -z "$max_children" ]; then 294 | # let's be safe with a minmal value 295 | sys_memory=$(free -m | grep -oP '\d+' | head -n 1) 296 | if (($sys_memory <= 600)) ; then 297 | max_children=4 298 | elif (($sys_memory <= 1600)) ; then 299 | max_children=6 300 | elif (($sys_memory <= 5600)) ; then 301 | max_children=10 302 | elif (($sys_memory <= 10600)) ; then 303 | PM_METHOD=static 304 | max_children=20 305 | else 306 | PM_METHOD=static 307 | max_children=50 308 | fi 309 | fi 310 | 311 | env_type=${ENV_TYPE:-""} 312 | if [[ $env_type = "local" ]]; then 313 | PM_METHOD=ondemand 314 | fi 315 | 316 | echo "Configuring memory limit to ${user_mem_limit}MB" 317 | sed -i -e '/^memory_limit/ s/=.*/= '$user_mem_limit'M/' $fpm_ini_file 318 | 319 | user_max_filesize=${PHP_MAX_FILESIZE:-64} 320 | echo "Configuring 'post_max_size' and 'upload_max_filesize' to ${user_max_filesize}MB..." 321 | sed -i -e '/^post_max_size/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 322 | sed -i -e '/^upload_max_filesize/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 323 | 324 | user_max_input_vars=${PHP_MAX_INPUT_VARS:-5000} 325 | echo "Configuring 'max_input_vars' to $user_max_input_vars (from the default 1000)..." 326 | sed -i '/max_input_vars/ s/;\? \?\(max_input_vars \?= \?\)[[:digit:]]\+/\1'$user_max_input_vars'/' $fpm_ini_file 327 | 328 | # Setup timezone 329 | user_timezone=${USER_TIMEZONE:-UTC} 330 | echo "Configuring timezone to $user_timezone ..." 331 | sed -i -e 's/^;date\.timezone =$/date.timezone = "'$user_timezone'"/' $fpm_ini_file 332 | export PHP_PCNTL_FUNCTIONS='pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare' 333 | export PHP_EXEC_FUNCTIONS='escapeshellarg,escapeshellcmd,exec,passthru,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,shell_exec,system' 334 | sed -i "/disable_functions/c disable_functions = ${PHP_PCNTL_FUNCTIONS},${PHP_EXEC_FUNCTIONS}" $fpm_ini_file 335 | 336 | [ ! -f $pool_file ] && cp /etc/php/${php_ver}/fpm/pool.d/www.conf $pool_file 337 | [ -f /etc/php/${php_ver}/fpm/pool.d/www.conf ] && mv /etc/php/${php_ver}/fpm/pool.d/www.conf ~/backups/php-www.conf-$(date +%F) 338 | sed -i -e 's/^\[www\]$/['$php_user']/' $pool_file 339 | sed -i -e 's/www-data/'$php_user'/' $pool_file 340 | sed -i -e '/^;listen.\(owner\|group\|mode\)/ s/^;//' $pool_file 341 | sed -i -e '/^listen.mode = / s/[0-9]\{4\}/0666/' $pool_file 342 | 343 | sed -i -e 's/^pm = .*/pm = '$PM_METHOD'/' $pool_file 344 | sed -i '/^pm.max_children/ s/=.*/= '$max_children'/' $pool_file 345 | 346 | PHP_MIN=$(expr $max_children / 10) 347 | 348 | sed -i '/^;catch_workers_output/ s/^;//' $pool_file 349 | sed -i '/^;pm.process_idle_timeout/ s/^;//' $pool_file 350 | sed -i '/^;pm.max_requests/ s/^;//' $pool_file 351 | sed -i '/^;pm.status_path/ s/^;//' $pool_file 352 | sed -i '/^;ping.path/ s/^;//' $pool_file 353 | sed -i '/^;ping.response/ s/^;//' $pool_file 354 | 355 | # home_basename=web 356 | # home_basename=$(echo $wp_user | awk -F _ '{print $1}') 357 | # [ -z $home_basename ] && home_basename=web 358 | # [ ! -d /home/${home_basename}/log ] && mkdir /home/${home_basename}/log 359 | PHP_SLOW_LOG_PATH="/var/log/slow-php.log" 360 | sed -i '/^;slowlog/ s/^;//' $pool_file 361 | sed -i '/^slowlog/ s:=.*$: = '$PHP_SLOW_LOG_PATH':' $pool_file 362 | sed -i '/^;request_slowlog_timeout/ s/^;//' $pool_file 363 | sed -i '/^request_slowlog_timeout/ s/= .*$/= 60/' $pool_file 364 | 365 | FPMCONF="/etc/php/${php_ver}/fpm/php-fpm.conf" 366 | sed -i '/^;emergency_restart_threshold/ s/^;//' $FPMCONF 367 | sed -i '/^emergency_restart_threshold/ s/=.*$/= '$PHP_MIN'/' $FPMCONF 368 | sed -i '/^;emergency_restart_interval/ s/^;//' $FPMCONF 369 | sed -i '/^emergency_restart_interval/ s/=.*$/= 1m/' $FPMCONF 370 | sed -i '/^;process_control_timeout/ s/^;//' $FPMCONF 371 | sed -i '/^process_control_timeout/ s/=.*$/= 10s/' $FPMCONF 372 | 373 | # restart php upon OOM or other failures 374 | # ref: https://stackoverflow.com/a/45107512/1004587 375 | # TODO: Do the following only if "Restart=on-failure" is not found in that file. 376 | sed -i '/^\[Service\]/!b;:a;n;/./ba;iRestart=on-failure' /lib/systemd/system/php${php_ver}-fpm.service 377 | systemctl daemon-reload 378 | check_result $? "Could not update /lib/systemd/system/php${php_ver}-fpm.service file!" 379 | 380 | printf '%-72s' "Restarting PHP-FPM..." 381 | /usr/sbin/php-fpm${php_ver} -t 2>/dev/null && systemctl restart php${php_ver}-fpm 382 | echo done. 383 | 384 | # echo -------------------------------- Nginx ---------------------------------------- 385 | 386 | # Download WordPress Nginx repo 387 | [ ! -d ~/wp-nginx ] && { 388 | mkdir ~/wp-nginx 389 | wget -q -O- https://github.com/pothi/wordpress-nginx/tarball/main | tar -xz -C ~/wp-nginx --strip-components=1 390 | cp -a ~/wp-nginx/{conf.d,errors,globals,sites-available} /etc/nginx/ 391 | [ ! -d /etc/nginx/sites-enabled ] && mkdir /etc/nginx/sites-enabled 392 | ln -fs /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/default.conf 393 | } 394 | 395 | # Remove the default conf file supplied by OS 396 | [ -f /etc/nginx/sites-enabled/default ] && rm /etc/nginx/sites-enabled/default 397 | 398 | # Remove the default SSL conf to support latest SSL conf. 399 | # It should hide two lines starting with ssl_ 400 | # ^ starting with... 401 | # \s* matches any number of space or tab elements before ssl_ 402 | # when run more than once, it just doesn't do anything as the start of the line is '#' after the first execution. 403 | sed -i 's/^\s*ssl_/# &/' /etc/nginx/nginx.conf 404 | 405 | # create dhparam 406 | if [ ! -f /etc/nginx/dhparam.pem ]; then 407 | openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096 &> /dev/null 408 | sed -i 's:^# \(ssl_dhparam /etc/nginx/dhparam.pem;\)$:\1:' /etc/nginx/conf.d/ssl-common.conf 409 | fi 410 | 411 | # if php_ver is 6.0 then php_ver_short is 60 412 | php_ver_short=$(echo $php_ver | sed 's/\.//') 413 | socket=/run/php/fpm-${php_ver_short}-${php_user}.sock 414 | sed -i "/^listen =/ s:=.*:= $socket:" $pool_file 415 | # [ -f /etc/nginx/conf.d/lb.conf ] && sed -i "s:/var/lock/php-fpm.*;:$socket;:" /etc/nginx/conf.d/lb.conf 416 | [ -f /etc/nginx/conf.d/lb.conf ] && rm /etc/nginx/conf.d/lb.conf 417 | [ ! -f /etc/nginx/conf.d/fpm.conf ] && echo "upstream fpm { server unix:$socket; }" > /etc/nginx/conf.d/fpm.conf 418 | [ ! -f /etc/nginx/conf.d/fpm${php_ver_short}.conf ] && echo "upstream fpm${php_ver_short} { server unix:$socket; }" > /etc/nginx/conf.d/fpm${php_ver_short}.conf 419 | 420 | printf '%-72s' "Restarting Nginx..." 421 | /usr/sbin/nginx -t 2>/dev/null && systemctl restart nginx 422 | echo done. 423 | 424 | echo --------------------------- Certbot ----------------------------------------- 425 | snap install core 426 | snap refresh core 427 | apt-get -qq remove certbot 428 | snap install --classic certbot 429 | ln -fs /snap/bin/certbot /usr/bin/certbot 430 | 431 | # register certbot account if email is supplied 432 | if [ $CERTBOT_ADMIN_EMAIL ]; then 433 | certbot show_account &> /dev/null 434 | if [ "$?" != "0" ]; then 435 | certbot -m $CERTBOT_ADMIN_EMAIL --agree-tos --no-eff-email register 436 | fi 437 | fi 438 | 439 | # Restart script upon renewal; it can also alert upon success or failure 440 | # See - https://github.com/pothi/snippets/blob/main/ssl/nginx-restart.sh 441 | [ ! -d /etc/letsencrypt/renewal-hooks/deploy/ ] && mkdir -p /etc/letsencrypt/renewal-hooks/deploy/ 442 | restart_script=/etc/letsencrypt/renewal-hooks/deploy/nginx-restart.sh 443 | restart_script_url=https://github.com/pothi/snippets/raw/main/ssl/nginx-restart.sh 444 | [ ! -f "$restart_script" ] && { 445 | curl -sSL --create-dirs -o $restart_script $restart_script_url 446 | check_result $? "Could not download Nginx Restart Script for Certbot renewals." 447 | chmod +x $restart_script 448 | } 449 | 450 | [ -d ~/backup/etc-certbot-default ] || cp -a /etc ~/backups/etc-certbot-default 451 | 452 | #--- Additional Steps ---# 453 | # ~/.ssh tweaks 454 | if [ ! -d /home/${home_basename}/.ssh ]; then 455 | mkdir /home/${home_basename}/.ssh 456 | chmod 700 /home/${home_basename}/.ssh 457 | chown ${wp_user}:${wp_user} /home/${home_basename}/.ssh 458 | fi 459 | cp -a ~/.ssh/authorized_keys /home/${home_basename}/.ssh 460 | chown ${wp_user}:${wp_user} /home/${home_basename}/.ssh/* 461 | 462 | # bootstrap root user 463 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/bootstrap-root.sh 464 | bash bootstrap-root.sh && rm bootstrap-root.sh 465 | check_result $? "Could not bootstrap root." 466 | 467 | #-------------------- Install mta (postfix) --------------------# 468 | if ! command -v mail >/dev/null; then 469 | printf '%-72s' "Installing MTA (postfix)..." 470 | [ -f email-mta-installation.sh ] && rm email-mta-installation.sh 471 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/email-mta-installation.sh 472 | bash email-mta-installation.sh > /dev/null 473 | check_result $? "Could not install MTA(postfix)." 474 | [ -f email-mta-installation.sh ] && rm email-mta-installation.sh 475 | echo done. 476 | fi 477 | 478 | # bootstrap unattended upgrades and reboots 479 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/unattended-upgrades.sh 480 | bash unattended-upgrades.sh && rm unattended-upgrades.sh 481 | check_result $? "Could not bootstrap Unattended Upgrades script." 482 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/unattended-reboots.sh 483 | bash unattended-reboots.sh && rm unattended-reboots.sh 484 | check_result $? "Could not bootstrap Unattended Reboot script." 485 | 486 | printf '%-72s' "Running apt upgrade... it may take sometime..." 487 | apt-get -qq upgrade > /dev/null 488 | check_result $? "Could not run 'apt upgrade'." 489 | echo done. 490 | 491 | # bootstrap WP user, install wp-cli, aws-cli, etc. 492 | # TODO: goes through with warnings and errors! 493 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/bootstrap-wp-user.sh 494 | chown ${wp_user} bootstrap-wp-user.sh 495 | chmod +x bootstrap-wp-user.sh 496 | mv bootstrap-wp-user.sh /home/${home_basename}/ 497 | sudo -H -u "${wp_user}" /home/${home_basename}/bootstrap-wp-user.sh && rm /home/${home_basename}/bootstrap-wp-user.sh 498 | check_result $? "Could not bootstrap WP user." 499 | 500 | echo All done. 501 | 502 | echo ----------------------------------------------------------------------------- 503 | echo You may find the login credentials of SFTP/SSH user in /root/.envrc file. 504 | echo ----------------------------------------------------------------------------- 505 | 506 | echo 'You may reboot (only) once to apply certain updates (ex: kernel updates)!' 507 | echo 508 | 509 | echo "Script ended on (date & time): $(date +%c)" 510 | echo 511 | -------------------------------------------------------------------------------- /bootstrap-debian-bullseye.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # Version: 3.1 7 | 8 | # this is the PHP version that comes by default with the current Ubuntu LTS 9 | php_ver=7.4 10 | 11 | # to be run as root, probably as a user-script just after a server is installed 12 | # https://stackoverflow.com/a/52586842/1004587 13 | # also see https://stackoverflow.com/q/3522341/1004587 14 | is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; } 15 | [ is_user_root ] || { echo 'You must be root or have sudo privilege to run this script. Exiting now.'; exit 1; } 16 | 17 | export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin 18 | export DEBIAN_FRONTEND=noninteractive 19 | 20 | echo "Script started on (date & time): $(date +%c)" 21 | 22 | [ -d ~/backups ] || mkdir ~/backups 23 | 24 | # helper function to exit upon non-zero exit code of a command 25 | # usage some_command; check_result $? 'some_command failed' 26 | if ! $(type 'check_result' 2>/dev/null | grep -q 'function') ; then 27 | check_result() { 28 | if [ "$1" -ne 0 ]; then 29 | echo -e "\nError: $2. Exiting!\n" 30 | exit "$1" 31 | fi 32 | } 33 | fi 34 | 35 | # function to configure timezone to UTC 36 | set_utc_timezone() { 37 | if [ "$(date +\%Z)" != "UTC" ] ; then 38 | [ ! -f /usr/sbin/tzconfig ] && apt-get -qq install tzdata > /dev/null 39 | printf '%-72s' "Setting up timezone..." 40 | ln -fs /usr/share/zoneinfo/UTC /etc/localtime 41 | dpkg-reconfigure -f noninteractive tzdata 42 | # timedatectl set-timezone UTC 43 | check_result $? 'Error setting up timezone.' 44 | 45 | # Recommended to restart cron after every change in timezone 46 | systemctl restart cron 47 | check_result $? 'Error restarting cron daemon after changing timezone.' 48 | echo done. 49 | fi 50 | } 51 | 52 | # if ~/.envrc doesn't exist, create it 53 | if [ ! -f "$HOME/.envrc" ]; then 54 | touch ~/.envrc 55 | chmod 600 ~/.envrc 56 | # if exists, source it to apply the env variables 57 | else 58 | . ~/.envrc 59 | fi 60 | 61 | echo "export PHP_VERSION=$php_ver" >> /root/.envrc 62 | 63 | #--- swap ---# 64 | if free | awk '/^Swap:/ {exit !$2}'; then 65 | # echo 'Swap already exists!' 66 | : 67 | else 68 | printf '%-72s' "Creating swap..." 69 | wget -O /tmp/swap.sh -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/swap.sh 70 | bash /tmp/swap.sh 71 | rm /tmp/swap.sh 72 | echo done. 73 | fi 74 | 75 | #--- apt tweaks ---# 76 | 77 | # Ref: https://wiki.debian.org/Multiarch/HOWTO 78 | # https://askubuntu.com/a/1336013/65814 79 | [ ! $(dpkg --get-selections | grep -q i386) ] && dpkg --remove-architecture i386 2>/dev/null 80 | 81 | # Fix apt ipv4/6 issue 82 | [ ! -f /etc/apt/apt.conf.d/1000-force-ipv4-transport ] && \ 83 | echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/1000-force-ipv4-transport 84 | 85 | # Fix a warning related to dialog 86 | # run `debconf-show debconf` to see the current /default selections. 87 | echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 88 | 89 | # the following runs when apt cache is older than 6 hours 90 | # Taken from Ansible - https://askubuntu.com/a/1362550/65814 91 | APT_UPDATE_SUCCESS_STAMP_PATH=/var/lib/apt/periodic/update-success-stamp 92 | APT_LISTS_PATH=/var/lib/apt/lists 93 | if [ -f "$APT_UPDATE_SUCCESS_STAMP_PATH" ]; then 94 | if [ -z "$(find "$APT_UPDATE_SUCCESS_STAMP_PATH" -mmin -360 2> /dev/null)" ]; then 95 | printf '%-72s' "Updating apt cache" 96 | apt-get -qq update 97 | echo done. 98 | fi 99 | elif [ -d "$APT_LISTS_PATH" ]; then 100 | if [ -z "$(find "$APT_LISTS_PATH" -mmin -360 2> /dev/null)" ]; then 101 | printf '%-72s' "Updating apt cache" 102 | apt-get -qq update 103 | echo done. 104 | fi 105 | fi 106 | 107 | # ref: https://www.server-world.info/en/note?os=Debian_10&p=locale 108 | lang=$LANG 109 | if [ "$lang" != "en_US.UTF-8" ]; then 110 | if dpkg-query -W -f='${Status}' locales-all 2>/dev/null | grep -q "ok installed" ; then : 111 | else 112 | apt-get -qq install locales-all 113 | fi 114 | localectl set-locale LANG=en_US.UTF-8 115 | source /etc/default/locale 116 | fi 117 | 118 | # -------------------------- Prerequisites ------------------------------------ 119 | 120 | # apt-utils to fix an annoying non-critical bug on minimal images. Ref: https://github.com/tianon/docker-brew-ubuntu-core/issues/59 121 | apt-get -qq install apt-utils &> /dev/null 122 | 123 | required_packages="apt-transport-https \ 124 | curl \ 125 | dnsutils \ 126 | fail2ban \ 127 | git \ 128 | pwgen \ 129 | python3-venv \ 130 | snapd \ 131 | software-properties-common \ 132 | sudo \ 133 | unzip \ 134 | wget" 135 | 136 | for package in $required_packages 137 | do 138 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 139 | then 140 | # echo "'$package' is already installed" 141 | : 142 | else 143 | printf '%-72s' "Installing '${package}' ..." 144 | apt-get -qq install $package > /dev/null 145 | check_result $? "Error: couldn't install $package." 146 | echo done. 147 | fi 148 | done 149 | 150 | #--- setup timezone ---# 151 | set_utc_timezone 152 | 153 | # Create a WordPress user with /home/web as $HOME 154 | wp_user=${WP_USERNAME:-""} 155 | if [ "$wp_user" == "" ]; then 156 | printf '%-72s' "Creating a WP User..." 157 | wp_user="wp_$(openssl rand -base64 32 | tr -d /=+ | cut -c -10)" 158 | echo "export WP_USERNAME=$wp_user" >> /root/.envrc 159 | echo done. 160 | fi 161 | 162 | # home_basename=$(echo $wp_user | awk -F _ '{print $1}') 163 | # [ -z $home_basename ] && home_basename=web 164 | home_basename=web 165 | 166 | useradd --shell=/bin/bash -m --home-dir /home/${home_basename} $wp_user 167 | chmod 755 /home/$home_basename 168 | 169 | groupadd ${home_basename} 170 | gpasswd -a $wp_user ${home_basename} > /dev/null 171 | 172 | # Create password for WP User 173 | wp_pass=${WP_PASSWORD:-""} 174 | if [ "$wp_pass" == "" ]; then 175 | printf '%-72s' "Creating password for WP user..." 176 | wp_pass=$(openssl rand -base64 32 | tr -d /=+ | cut -c -20) 177 | echo "export WP_PASSWORD=$wp_pass" >> /root/.envrc 178 | echo done. 179 | fi 180 | 181 | echo "$wp_user:$wp_pass" | chpasswd 182 | 183 | # provide sudo access without passwd to WP User 184 | if [ ! -f /etc/sudoers.d/$wp_user ]; then 185 | printf '%-72s' "Providing sudo privilege for WP user..." 186 | echo "${wp_user} ALL=(ALL) NOPASSWD:ALL"> /etc/sudoers.d/$wp_user 187 | chmod 400 /etc/sudoers.d/$wp_user 188 | echo done. 189 | fi 190 | 191 | # Enable password authentication for WP User 192 | cd /etc/ssh/sshd_config.d 193 | if [ ! -f enable-passwd-auth.conf ]; then 194 | printf '%-72s' "Enabling Password Authentication for WP user..." 195 | echo "PasswordAuthentication yes" > enable-passwd-auth.conf 196 | /usr/sbin/sshd -t && systemctl restart sshd 197 | check_result $? 'Error restarting SSH daemon while enabling passwd auth.' 198 | echo done. 199 | fi 200 | cd - 1> /dev/null 201 | 202 | echo ---------------------------------- LEMP ------------------------------------- 203 | 204 | # ------------------------------- MySQL --------------------------------------- 205 | # MySQL is required by PHP. So, install it before PHP 206 | 207 | package=default-mysql-server 208 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 209 | then 210 | # echo "'$package' is already installed." 211 | : 212 | else 213 | printf '%-72s' "Installing '${package}' ..." 214 | apt-get -qq install $package > /dev/null 215 | check_result $? "Error: couldn't install $package." 216 | echo done. 217 | fi 218 | 219 | # Create a MySQL admin user 220 | sql_user=${MYSQL_ADMIN_USER:-""} 221 | if [ "$sql_user" == "" ]; then 222 | printf '%-72s' "Creating a MySQL Admin User..." 223 | # create MYSQL username automatically 224 | # unique username / password generator: https://unix.stackexchange.com/q/230673/20241 225 | sql_user="mysql_$(openssl rand -base64 32 | tr -d /=+ | cut -c -10)" 226 | echo "export MYSQL_ADMIN_USER=$sql_user" >> /root/.envrc 227 | echo done. 228 | fi 229 | 230 | sql_pass=${MYSQL_ADMIN_PASS:-""} 231 | if [ "$sql_pass" == "" ]; then 232 | sql_pass=$(openssl rand -base64 32 | tr -d /=+ | cut -c -20) 233 | echo "export MYSQL_ADMIN_PASS=$sql_pass" >> /root/.envrc 234 | fi 235 | 236 | mysql -e "CREATE USER IF NOT EXISTS ${sql_user} IDENTIFIED BY '${sql_pass}';" 237 | mysql -e "GRANT ALL PRIVILEGES ON *.* TO ${sql_user} WITH GRANT OPTION" 238 | 239 | echo -------------------------------- PHP ---------------------------------------- 240 | 241 | # PHP is required by Nginx to configure the defaults. So, install it along with Nginx 242 | 243 | lemp_packages="nginx-extras \ 244 | php${php_ver}-fpm \ 245 | php${php_ver}-mysql \ 246 | php${php_ver}-gd \ 247 | php${php_ver}-cli \ 248 | php${php_ver}-xml \ 249 | php${php_ver}-mbstring \ 250 | php${php_ver}-soap \ 251 | php${php_ver}-curl \ 252 | php${php_ver}-zip \ 253 | php${php_ver}-bcmath \ 254 | php${php_ver}-intl \ 255 | php${php_ver}-imagick" 256 | 257 | for package in $lemp_packages 258 | do 259 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 260 | then 261 | # echo "'$package' is already installed" 262 | : 263 | else 264 | # Remove ${php_ver} from package name to find if php-package is installed. 265 | php_package=$(printf '%s' "$package" | sed 's/[.0-9]*//g') 266 | if dpkg-query -W -f='${Status}' $php_package 2>/dev/null | grep -q "ok installed" 267 | then 268 | echo "'$package' is already installed as $php_package" 269 | : 270 | else 271 | printf '%-72s' "Installing '${package}' ..." 272 | apt-get -qq install $package > /dev/null 273 | check_result $? "Error installing ${package}." 274 | echo done. 275 | fi 276 | fi 277 | done 278 | 279 | # Download WordPress Nginx repo 280 | [ ! -d ~/wp-nginx ] && { 281 | mkdir ~/wp-nginx 282 | wget -q -O- https://github.com/pothi/wordpress-nginx/tarball/main | tar -xz -C ~/wp-nginx --strip-components=1 283 | cp -a ~/wp-nginx/{conf.d,errors,globals,sites-available} /etc/nginx/ 284 | [ ! -d /etc/nginx/sites-enabled ] && mkdir /etc/nginx/sites-enabled 285 | ln -fs /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/default.conf 286 | } 287 | 288 | # Remove the default conf file supplied by OS 289 | [ -f /etc/nginx/sites-enabled/default ] && rm /etc/nginx/sites-enabled/default 290 | 291 | # Remove the default SSL conf to support latest SSL conf. 292 | # It should hide two lines starting with ssl_ 293 | # ^ starting with... 294 | # \s* matches any number of space or tab elements before ssl_ 295 | # when run more than once, it just doesn't do anything as the start of the line is '#' after the first execution. 296 | sed -i 's/^\s*ssl_/# &/' /etc/nginx/nginx.conf 297 | 298 | # create dhparam 299 | if [ ! -f /etc/nginx/dhparam.pem ]; then 300 | openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096 &> /dev/null 301 | sed -i 's:^# \(ssl_dhparam /etc/nginx/dhparam.pem;\)$:\1:' /etc/nginx/conf.d/ssl-common.conf 302 | fi 303 | 304 | echo ----------------------------------------------------------------------------- 305 | echo "Please check ~/.envrc for all the credentials." 306 | echo ----------------------------------------------------------------------------- 307 | 308 | # . $local_wp_in_a_box_repo/scripts/php-installation.sh 309 | php_user=$wp_user 310 | fpm_ini_file=/etc/php/${php_ver}/fpm/php.ini 311 | pool_file=/etc/php/${php_ver}/fpm/pool.d/${php_user}.conf 312 | PM_METHOD=ondemand 313 | 314 | user_mem_limit=${PHP_MEM_LIMIT:-""} 315 | [ -z "$user_mem_limit" ] && user_mem_limit=512 316 | 317 | max_children=${PHP_MAX_CHILDREN:-""} 318 | 319 | if [ -z "$max_children" ]; then 320 | # let's be safe with a minmal value 321 | sys_memory=$(free -m | grep -oP '\d+' | head -n 1) 322 | if (($sys_memory <= 600)) ; then 323 | max_children=4 324 | elif (($sys_memory <= 1600)) ; then 325 | max_children=6 326 | elif (($sys_memory <= 5600)) ; then 327 | max_children=10 328 | elif (($sys_memory <= 10600)) ; then 329 | PM_METHOD=static 330 | max_children=20 331 | elif (($sys_memory <= 20600)) ; then 332 | PM_METHOD=static 333 | max_children=40 334 | elif (($sys_memory <= 30600)) ; then 335 | PM_METHOD=static 336 | max_children=60 337 | elif (($sys_memory <= 40600)) ; then 338 | PM_METHOD=static 339 | max_children=80 340 | else 341 | PM_METHOD=static 342 | max_children=100 343 | fi 344 | fi 345 | 346 | env_type=${ENV_TYPE:-""} 347 | if [[ $env_type = "local" ]]; then 348 | PM_METHOD=ondemand 349 | fi 350 | 351 | echo "Configuring memory limit to ${user_mem_limit}MB" 352 | sed -i -e '/^memory_limit/ s/=.*/= '$user_mem_limit'M/' $fpm_ini_file 353 | 354 | user_max_filesize=${PHP_MAX_FILESIZE:-64} 355 | echo "Configuring 'post_max_size' and 'upload_max_filesize' to ${user_max_filesize}MB..." 356 | sed -i -e '/^post_max_size/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 357 | sed -i -e '/^upload_max_filesize/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 358 | 359 | user_max_input_vars=${PHP_MAX_INPUT_VARS:-5000} 360 | echo "Configuring 'max_input_vars' to $user_max_input_vars (from the default 1000)..." 361 | sed -i '/max_input_vars/ s/;\? \?\(max_input_vars \?= \?\)[[:digit:]]\+/\1'$user_max_input_vars'/' $fpm_ini_file 362 | 363 | # Setup timezone 364 | user_timezone=${USER_TIMEZONE:-UTC} 365 | echo "Configuring timezone to $user_timezone ..." 366 | sed -i -e 's/^;date\.timezone =$/date.timezone = "'$user_timezone'"/' $fpm_ini_file 367 | export PHP_PCNTL_FUNCTIONS='pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare' 368 | export PHP_EXEC_FUNCTIONS='escapeshellarg,escapeshellcmd,exec,passthru,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,shell_exec,system' 369 | sed -i "/disable_functions/c disable_functions = ${PHP_PCNTL_FUNCTIONS},${PHP_EXEC_FUNCTIONS}" $fpm_ini_file 370 | 371 | [ ! -f $pool_file ] && cp /etc/php/${php_ver}/fpm/pool.d/www.conf $pool_file 372 | [ -f /etc/php/${php_ver}/fpm/pool.d/www.conf ] && mv /etc/php/${php_ver}/fpm/pool.d/www.conf ~/backups/php-www.conf-$(date +%F) 373 | sed -i -e 's/^\[www\]$/['$php_user']/' $pool_file 374 | sed -i -e 's/www-data/'$php_user'/' $pool_file 375 | sed -i -e '/^;listen.\(owner\|group\|mode\)/ s/^;//' $pool_file 376 | sed -i -e '/^listen.mode = / s/[0-9]\{4\}/0666/' $pool_file 377 | 378 | php_ver_short=$(echo $php_ver | sed 's/\.//') 379 | socket=/run/php/fpm-${php_ver_short}-${php_user}.sock 380 | sed -i "/^listen =/ s:=.*:= $socket:" $pool_file 381 | # [ -f /etc/nginx/conf.d/lb.conf ] && sed -i "s:/var/lock/php-fpm.*;:$socket;:" /etc/nginx/conf.d/lb.conf 382 | [ -f /etc/nginx/conf.d/lb.conf ] && rm /etc/nginx/conf.d/lb.conf 383 | [ ! -f /etc/nginx/conf.d/fpm.conf ] && echo "upstream fpm { server unix:$socket; }" > /etc/nginx/conf.d/fpm.conf 384 | [ ! -f /etc/nginx/conf.d/fpm${php_ver_short}.conf ] && echo "upstream fpm${php_ver_short} { server unix:$socket; }" > /etc/nginx/conf.d/fpm${php_ver_short}.conf 385 | 386 | sed -i -e 's/^pm = .*/pm = '$PM_METHOD'/' $pool_file 387 | sed -i '/^pm.max_children/ s/=.*/= '$max_children'/' $pool_file 388 | 389 | PHP_MIN=$(expr $max_children / 10) 390 | 391 | sed -i '/^;catch_workers_output/ s/^;//' $pool_file 392 | sed -i '/^;pm.process_idle_timeout/ s/^;//' $pool_file 393 | sed -i '/^;pm.max_requests/ s/^;//' $pool_file 394 | sed -i '/^;pm.status_path/ s/^;//' $pool_file 395 | sed -i '/^;ping.path/ s/^;//' $pool_file 396 | sed -i '/^;ping.response/ s/^;//' $pool_file 397 | 398 | # home_basename=web 399 | # home_basename=$(echo $wp_user | awk -F _ '{print $1}') 400 | # [ -z $home_basename ] && home_basename=web 401 | # [ ! -d /home/${home_basename}/log ] && mkdir /home/${home_basename}/log 402 | PHP_SLOW_LOG_PATH="/var/log/slow-php.log" 403 | sed -i '/^;slowlog/ s/^;//' $pool_file 404 | sed -i '/^slowlog/ s:=.*$: = '$PHP_SLOW_LOG_PATH':' $pool_file 405 | sed -i '/^;request_slowlog_timeout/ s/^;//' $pool_file 406 | sed -i '/^request_slowlog_timeout/ s/= .*$/= 60/' $pool_file 407 | 408 | FPMCONF="/etc/php/${php_ver}/fpm/php-fpm.conf" 409 | sed -i '/^;emergency_restart_threshold/ s/^;//' $FPMCONF 410 | sed -i '/^emergency_restart_threshold/ s/=.*$/= '$PHP_MIN'/' $FPMCONF 411 | sed -i '/^;emergency_restart_interval/ s/^;//' $FPMCONF 412 | sed -i '/^emergency_restart_interval/ s/=.*$/= 1m/' $FPMCONF 413 | sed -i '/^;process_control_timeout/ s/^;//' $FPMCONF 414 | sed -i '/^process_control_timeout/ s/=.*$/= 10s/' $FPMCONF 415 | 416 | # restart php upon OOM or other failures 417 | # ref: https://stackoverflow.com/a/45107512/1004587 418 | # TODO: Do the following only if "Restart=on-failure" is not found in that file. 419 | sed -i '/^\[Service\]/!b;:a;n;/./ba;iRestart=on-failure' /lib/systemd/system/php${php_ver}-fpm.service 420 | systemctl daemon-reload 421 | check_result $? "Could not update /lib/systemd/system/php${php_ver}-fpm.service file!" 422 | 423 | printf '%-72s' "Restarting PHP-FPM..." 424 | /usr/sbin/php-fpm${php_ver} -t 2>/dev/null && systemctl restart php${php_ver}-fpm 425 | echo done. 426 | 427 | printf '%-72s' "Restarting Nginx..." 428 | /usr/sbin/nginx -t 2>/dev/null && systemctl restart nginx 429 | echo done. 430 | 431 | echo --------------------------- Certbot ----------------------------------------- 432 | snap install core 433 | snap refresh core 434 | apt-get -qq remove certbot 435 | snap install --classic certbot 436 | ln -fs /snap/bin/certbot /usr/bin/certbot 437 | 438 | # register certbot account if email is supplied 439 | if [ $CERTBOT_ADMIN_EMAIL ]; then 440 | certbot show_account &> /dev/null 441 | if [ "$?" != "0" ]; then 442 | certbot -m $CERTBOT_ADMIN_EMAIL --agree-tos --no-eff-email register 443 | fi 444 | fi 445 | 446 | # Restart script upon renewal; it can also alert upon success or failure 447 | # See - https://github.com/pothi/snippets/blob/main/ssl/nginx-restart.sh 448 | [ ! -d /etc/letsencrypt/renewal-hooks/deploy/ ] && mkdir -p /etc/letsencrypt/renewal-hooks/deploy/ 449 | restart_script=/etc/letsencrypt/renewal-hooks/deploy/nginx-restart.sh 450 | restart_script_url=https://github.com/pothi/snippets/raw/main/ssl/nginx-restart.sh 451 | [ ! -f "$restart_script" ] && { 452 | curl -sSL --create-dirs -o $restart_script $restart_script_url 453 | check_result $? "Error downloading Nginx Restart Script for Certbot renewals." 454 | chmod +x $restart_script 455 | } 456 | 457 | echo All done. 458 | 459 | echo ----------------------------------------------------------------------------- 460 | echo You may find the login credentials of SFTP/SSH user in /root/.envrc file. 461 | echo ----------------------------------------------------------------------------- 462 | 463 | echo 'You may reboot only once to apply certain updates (ex: kernel updates)!' 464 | echo 465 | 466 | echo "Script ended on (date & time): $(date +%c)" 467 | echo 468 | -------------------------------------------------------------------------------- /bootstrap-debian-buster.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # Version: 1.1 7 | 8 | # to be run as root, probably as a user-script just after a server is installed 9 | # https://stackoverflow.com/a/52586842/1004587 10 | # also see https://stackoverflow.com/q/3522341/1004587 11 | is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; } 12 | [ is_user_root ] || { echo 'You must be root or have sudo privilege to run this script. Exiting now.'; exit 1; } 13 | 14 | echo "Script started on (date & time): $(date +%c)" 15 | 16 | # helper function to exit upon non-zero exit code of a command 17 | # usage some_command; check_result $? 'some_command failed' 18 | if ! $(type 'check_result' 2>/dev/null | grep -q 'function') ; then 19 | check_result() { 20 | if [ "$1" -ne 0 ]; then 21 | echo -e "\nError: $2. Exiting!\n" 22 | exit "$1" 23 | fi 24 | } 25 | fi 26 | 27 | [ ! -f "$HOME/.envrc" ] && touch ~/.envrc 28 | . ~/.envrc 29 | 30 | git_usermail=${EMAIL:-root@localhost} 31 | git_username=${NAME:-root} 32 | 33 | # Ref: https://wiki.debian.org/Multiarch/HOWTO 34 | # https://askubuntu.com/a/1336013/65814 35 | [ ! $(dpkg --get-selections | grep -q i386) ] && dpkg --remove-architecture i386 2>/dev/null 36 | 37 | # Fix apt ipv4/6 issue 38 | echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/1000-force-ipv4-transport 39 | 40 | export DEBIAN_FRONTEND=noninteractive 41 | # the following runs when apt cache is older than 6 hours 42 | if [ -z "$(find /var/cache/apt/pkgcache.bin -mmin -360 2> /dev/null)" ]; then 43 | printf '%-72s' "Updating apt cache" 44 | apt-get -qq update 45 | echo done. 46 | fi 47 | 48 | # ref: https://www.server-world.info/en/note?os=Debian_10&p=locale 49 | lang=$LANG 50 | if [ "$lang" != "en_US.UTF-8" ]; then 51 | if dpkg-query -W -f='${Status}' locales-all 2>/dev/null | grep -q "ok installed" ; then : 52 | else 53 | apt-get -qq install locales-all 54 | fi 55 | localectl set-locale LANG=en_US.UTF-8 56 | source /etc/default/locale 57 | fi 58 | 59 | echo -------------------------- Prerequisites ------------------------------------ 60 | required_packages="apt-transport-https \ 61 | curl \ 62 | dnsutils \ 63 | fail2ban \ 64 | git \ 65 | pwgen \ 66 | python3-venv \ 67 | snapd \ 68 | software-properties-common \ 69 | sudo \ 70 | tzdata \ 71 | unzip \ 72 | wget" 73 | 74 | for package in $required_packages 75 | do 76 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 77 | then 78 | # echo "'$package' is already installed" 79 | : 80 | else 81 | printf '%-72s' "Installing '${package}' ..." 82 | apt-get -qq install $package &> /dev/null 83 | 84 | # fix for apt refresh on first run. 85 | if [ "$?" -ne 0 ]; then 86 | apt-get update &> /dev/null 87 | apt-get -qq install $package &> /dev/null 88 | check_result $? "Couldn't install $package." 89 | fi 90 | 91 | echo done. 92 | fi 93 | done 94 | 95 | #--- setup timezone ---# 96 | current_time_zone=$(date +\%Z) 97 | if [ "$current_time_zone" != "UTC" ] ; then 98 | printf '%-72s' "Setting up timezone..." 99 | ln -fs /usr/share/zoneinfo/UTC /etc/localtime 100 | dpkg-reconfigure -f noninteractive tzdata 101 | # timedatectl set-timezone UTC 102 | check_result $? 'Error setting up timezone.' 103 | systemctl restart cron 104 | check_result $? 'Error restarting cron daemon.' 105 | echo done. 106 | fi 107 | 108 | echo ---------------------------------- LEMP ------------------------------------- 109 | 110 | php_ver=7.3 111 | lemp_packages="nginx-extras \ 112 | default-mysql-server \ 113 | php${php_ver}-fpm \ 114 | php${php_ver}-mysql \ 115 | php${php_ver}-gd \ 116 | php${php_ver}-cli \ 117 | php${php_ver}-xml \ 118 | php${php_ver}-mbstring \ 119 | php${php_ver}-soap \ 120 | php${php_ver}-curl \ 121 | php${php_ver}-zip \ 122 | php${php_ver}-bcmath \ 123 | php${php_ver}-intl \ 124 | php${php_ver}-imagick" 125 | 126 | for package in $lemp_packages 127 | do 128 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 129 | then 130 | # echo "'$package' is already installed" 131 | : 132 | else 133 | php_package=$(printf '%s' "$package" | sed 's/[.0-9]*//g') 134 | if dpkg-query -W -f='${Status}' $php_package 2>/dev/null | grep -q "ok installed" 135 | then 136 | echo "'$package' is already installed as $php_package" 137 | : 138 | else 139 | printf '%-72s' "Installing '${package}' ..." 140 | apt-get -qq install $package &> /dev/null 141 | check_result $? "Error installing ${package}." 142 | echo done. 143 | fi 144 | fi 145 | done 146 | 147 | [ ! -d ~/git/wordpress-nginx ] && { 148 | git clone -q https://github.com/pothi/wordpress-nginx ~/git/wordpress-nginx 149 | cp -a ~/git/wordpress-nginx/{conf.d,errors,globals,sites-available} /etc/nginx/ 150 | [ ! -d /etc/nginx/sites-enabled ] && mkdir /etc/nginx/sites-enabled 151 | ln -fs /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/default.conf 152 | } 153 | 154 | # create dhparam 155 | if [ ! -f /etc/nginx/dhparam.pem ]; then 156 | $(which openssl) dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096 &> /dev/null 157 | sed -i 's:^# \(ssl_dhparam /etc/nginx/dhparam.pem;\)$:\1:' /etc/nginx/conf.d/ssl-common.conf 158 | fi 159 | 160 | echo ----------------------------------------------------------------------------- 161 | echo "Please check ~/.envrc for all the credentials." 162 | echo ----------------------------------------------------------------------------- 163 | 164 | if [ "$ADMIN_USER" == "" ]; then 165 | printf '%-72s' "Creating a MySQL Admin User..." 166 | # create MYSQL username automatically 167 | ADMIN_USER="admin_$(pwgen -Av 6 1)" 168 | ADMIN_PASS=$(pwgen -cnsv 20 1) 169 | echo "export ADMIN_USER=$ADMIN_USER" >> /root/.envrc 170 | echo "export ADMIN_PASS=$ADMIN_PASS" >> /root/.envrc 171 | mysql -e "CREATE USER ${ADMIN_USER} IDENTIFIED BY '${ADMIN_PASS}';" 172 | mysql -e "GRANT ALL PRIVILEGES ON *.* TO ${ADMIN_USER} WITH GRANT OPTION" 173 | echo done. 174 | echo "Please check ~/.envrc for credentials." 175 | fi 176 | 177 | wp_user=${WP_USERNAME:-""} 178 | if [ "$wp_user" == "" ]; then 179 | printf '%-72s' "Creating a WP User..." 180 | wp_user="wp_$(pwgen -Av 9 1)" 181 | echo "export WP_USERNAME=$wp_user" >> /root/.envrc 182 | echo done. 183 | fi 184 | 185 | home_basename=$(echo $wp_user | awk -F _ '{print $1}') 186 | [ -z $home_basename ] && home_basename=web 187 | 188 | if [ ! -d "/home/${home_basename}" ]; then 189 | useradd --shell=/bin/bash -m --home-dir /home/${home_basename} $wp_user 190 | 191 | groupadd ${home_basename} 192 | gpasswd -a $wp_user ${home_basename} &> /dev/null 193 | fi 194 | 195 | wp_pass=${WP_PASSWORD:-""} 196 | if [ "$wp_pass" == "" ]; then 197 | printf '%-72s' "Creating password for WP user..." 198 | wp_pass=$(pwgen -cns 12 1) 199 | echo "export WP_PASSWORD=$wp_pass" >> /root/.envrc 200 | 201 | echo "$wp_user:$wp_pass" | chpasswd 202 | echo done. 203 | fi 204 | 205 | # provide sudo access without passwd to WP Dev 206 | if [ ! -f /etc/sudoers.d/$wp_user ]; then 207 | printf '%-72s' "Providing sudo privilege for WP user..." 208 | echo "${wp_user} ALL=(ALL) NOPASSWD:ALL"> /etc/sudoers.d/$wp_user 209 | chmod 400 /etc/sudoers.d/$wp_user 210 | echo done. 211 | fi 212 | 213 | if [ -d /etc/ssh/sshd_config.d ] ; then 214 | cd /etc/ssh/sshd_config.d 215 | if [ ! -f enable-passwd-auth.conf ]; then 216 | printf '%-72s' "Enabling Password Authentication for WP user..." 217 | echo "PasswordAuthentication yes" > enable-passwd-auth.conf 218 | /usr/sbin/sshd -t && systemctl restart sshd 219 | check_result $? 'Error restarting SSH daemon while enabling passwd auth.' 220 | echo done. 221 | fi 222 | cd - 1> /dev/null 223 | fi 224 | 225 | # . $local_wp_in_a_box_repo/scripts/php-installation.sh 226 | php_user=$wp_user 227 | fpm_ini_file=/etc/php/${php_ver}/fpm/php.ini 228 | pool_file=/etc/php/${php_ver}/fpm/pool.d/${php_user}.conf 229 | PM_METHOD=ondemand 230 | 231 | user_mem_limit=${PHP_MEM_LIMIT:-""} 232 | [ -z "$user_mem_limit" ] && user_mem_limit=256 233 | 234 | max_children=${PHP_MAX_CHILDREN:-""} 235 | 236 | if [ -z "$max_children" ]; then 237 | # let's be safe with a minmal value 238 | sys_memory=$(free -m | grep -oP '\d+' | head -n 1) 239 | if (($sys_memory <= 600)) ; then 240 | max_children=4 241 | elif (($sys_memory <= 1600)) ; then 242 | max_children=6 243 | elif (($sys_memory <= 5600)) ; then 244 | max_children=10 245 | elif (($sys_memory <= 10600)) ; then 246 | PM_METHOD=static 247 | max_children=20 248 | elif (($sys_memory <= 20600)) ; then 249 | PM_METHOD=static 250 | max_children=40 251 | elif (($sys_memory <= 30600)) ; then 252 | PM_METHOD=static 253 | max_children=60 254 | elif (($sys_memory <= 40600)) ; then 255 | PM_METHOD=static 256 | max_children=80 257 | else 258 | PM_METHOD=static 259 | max_children=100 260 | fi 261 | fi 262 | 263 | env_type=${ENV_TYPE:-""} 264 | if [[ $env_type = "local" ]]; then 265 | PM_METHOD=ondemand 266 | fi 267 | 268 | echo "Configuring memory limit to ${user_mem_limit}MB" 269 | sed -i -e '/^memory_limit/ s/=.*/= '$user_mem_limit'M/' $fpm_ini_file 270 | 271 | user_max_filesize=${PHP_MAX_FILESIZE:-64} 272 | echo "Configuring 'post_max_size' and 'upload_max_filesize' to ${user_max_filesize}MB..." 273 | sed -i -e '/^post_max_size/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 274 | sed -i -e '/^upload_max_filesize/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 275 | 276 | user_max_input_vars=${PHP_MAX_INPUT_VARS:-5000} 277 | echo "Configuring 'max_input_vars' to $user_max_input_vars (from the default 1000)..." 278 | sed -i '/max_input_vars/ s/;\? \?\(max_input_vars \?= \?\)[[:digit:]]\+/\1'$user_max_input_vars'/' $fpm_ini_file 279 | 280 | # Setup timezone 281 | user_timezone=${USER_TIMEZONE:-UTC} 282 | echo "Configuring timezone to $user_timezone ..." 283 | sed -i -e 's/^;date\.timezone =$/date.timezone = "'$user_timezone'"/' $fpm_ini_file 284 | export PHP_PCNTL_FUNCTIONS='pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare' 285 | export PHP_EXEC_FUNCTIONS='escapeshellarg,escapeshellcmd,exec,passthru,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,shell_exec,system' 286 | sed -i "/disable_functions/c disable_functions = ${PHP_PCNTL_FUNCTIONS},${PHP_EXEC_FUNCTIONS}" $fpm_ini_file 287 | 288 | [ ! -f $pool_file ] && cp /etc/php/${php_ver}/fpm/pool.d/www.conf $pool_file 289 | sed -i -e 's/^\[www\]$/['$php_user']/' $pool_file 290 | sed -i -e 's/www-data/'$php_user'/' $pool_file 291 | sed -i -e '/^;listen.\(owner\|group\|mode\)/ s/^;//' $pool_file 292 | sed -i -e '/^listen.mode = / s/[0-9]\{4\}/0666/' $pool_file 293 | 294 | php_ver_short=$(echo $php_ver | sed 's/\.//') 295 | socket=/run/php/fpm-${php_ver_short}-${php_user}.sock 296 | sed -i "/^listen =/ s:=.*:= $socket:" $pool_file 297 | [ -f /etc/nginx/conf.d/lb.conf ] && sed -i "s:/var/lock/php-fpm.*;:$socket;:" /etc/nginx/conf.d/lb.conf 298 | if [ ! -f /etc/nginx/conf.d/fpm${php_ver_short}.conf ]; then 299 | echo "upstream fpm${php_ver_short} { server unix:$socket; }" > /etc/nginx/conf.d/fpm${php_ver_short}.conf 300 | echo "upstream fpm { server unix:$socket; }" > /etc/nginx/conf.d/fpm.conf 301 | [ -f /etc/nginx/conf.d/lb.conf ] && rm /etc/nginx/conf.d/lb.conf 302 | fi 303 | 304 | sed -i -e 's/^pm = .*/pm = '$PM_METHOD'/' $pool_file 305 | sed -i '/^pm.max_children/ s/=.*/= '$max_children'/' $pool_file 306 | 307 | PHP_MIN=$(expr $max_children / 10) 308 | 309 | sed -i '/^;catch_workers_output/ s/^;//' $pool_file 310 | sed -i '/^;pm.process_idle_timeout/ s/^;//' $pool_file 311 | sed -i '/^;pm.max_requests/ s/^;//' $pool_file 312 | sed -i '/^;pm.status_path/ s/^;//' $pool_file 313 | sed -i '/^;ping.path/ s/^;//' $pool_file 314 | sed -i '/^;ping.response/ s/^;//' $pool_file 315 | 316 | # home_basename=web 317 | # home_basename=$(echo $wp_user | awk -F _ '{print $1}') 318 | # [ -z $home_basename ] && home_basename=web 319 | # [ ! -d /home/${home_basename}/log ] && mkdir /home/${home_basename}/log 320 | PHP_SLOW_LOG_PATH="/var/log/slow-php.log" 321 | sed -i '/^;slowlog/ s/^;//' $pool_file 322 | sed -i '/^slowlog/ s:=.*$: = '$PHP_SLOW_LOG_PATH':' $pool_file 323 | sed -i '/^;request_slowlog_timeout/ s/^;//' $pool_file 324 | sed -i '/^request_slowlog_timeout/ s/= .*$/= 60/' $pool_file 325 | 326 | FPMCONF="/etc/php/${php_ver}/fpm/php-fpm.conf" 327 | sed -i '/^;emergency_restart_threshold/ s/^;//' $FPMCONF 328 | sed -i '/^emergency_restart_threshold/ s/=.*$/= '$PHP_MIN'/' $FPMCONF 329 | sed -i '/^;emergency_restart_interval/ s/^;//' $FPMCONF 330 | sed -i '/^emergency_restart_interval/ s/=.*$/= 1m/' $FPMCONF 331 | sed -i '/^;process_control_timeout/ s/^;//' $FPMCONF 332 | sed -i '/^process_control_timeout/ s/=.*$/= 10s/' $FPMCONF 333 | 334 | # restart php upon OOM or other failures 335 | # ref: https://stackoverflow.com/a/45107512/1004587 336 | # TODO: Do the following only if "Restart=on-failure" is not found in that file. 337 | sed -i '/^\[Service\]/!b;:a;n;/./ba;iRestart=on-failure' /lib/systemd/system/php${php_ver}-fpm.service 338 | systemctl daemon-reload 339 | check_result $? "Could not update /lib/systemd/system/php${php_ver}-fpm.service file!" 340 | 341 | printf '%-72s' "Restarting PHP-FPM..." 342 | /usr/sbin/php-fpm${php_ver} -t 2>/dev/null && systemctl restart php${php_ver}-fpm 343 | echo done. 344 | 345 | printf '%-72s' "Restarting Nginx..." 346 | /usr/sbin/nginx -t 2>/dev/null && systemctl restart nginx 347 | echo done. 348 | 349 | echo --------------------------- Certbot ----------------------------------------- 350 | snap install core 351 | snap refresh core 352 | apt-get -qq remove certbot 353 | snap install --classic certbot 354 | ln -fs /snap/bin/certbot /usr/bin/certbot 355 | 356 | [ ! -d /etc/letsencrypt/renewal-hooks/deploy/ ] && mkdir -p /etc/letsencrypt/renewal-hooks/deploy/ 357 | restart_script=/etc/letsencrypt/renewal-hooks/deploy/nginx-restart.sh 358 | restart_script_url=https://github.com/pothi/snippets/raw/main/ssl/nginx-restart.sh 359 | [ ! -f "$restart_script" ] && { 360 | curl -sSL --create-dirs -o $restart_script $restart_script_url 361 | check_result $? "Error downloading Nginx Restart Script for Certbot renewals." 362 | chmod +x $restart_script 363 | } 364 | 365 | echo All done. 366 | 367 | echo ----------------------------------------------------------------------------- 368 | echo You may find the login credentials of SFTP/SSH user in /root/.envrc file. 369 | echo ----------------------------------------------------------------------------- 370 | 371 | echo 'You may reboot only once to apply certain updates (ex: kernel updates)!' 372 | echo 373 | 374 | echo "Script ended on (date & time): $(date +%c)" 375 | echo 376 | -------------------------------------------------------------------------------- /bootstrap-ubuntu-focal.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # Version: 1.1 7 | 8 | # to be run as root, probably as a user-script just after a server is installed 9 | # https://stackoverflow.com/a/52586842/1004587 10 | # also see https://stackoverflow.com/q/3522341/1004587 11 | is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; } 12 | [ is_user_root ] || { echo 'You must be root or have sudo privilege to run this script. Exiting now.'; exit 1; } 13 | 14 | echo "Script started on (date & time): $(date +%c)" 15 | 16 | # helper function to exit upon non-zero exit code of a command 17 | # usage some_command; check_result $? 'some_command failed' 18 | if ! $(type 'check_result' 2>/dev/null | grep -q 'function') ; then 19 | check_result() { 20 | if [ "$1" -ne 0 ]; then 21 | echo -e "\nError: $2. Exiting!\n" 22 | exit "$1" 23 | fi 24 | } 25 | fi 26 | 27 | [ ! -f "$HOME/.envrc" ] && touch ~/.envrc 28 | . ~/.envrc 29 | 30 | git_usermail=${EMAIL:-root@localhost} 31 | git_username=${NAME:-root} 32 | 33 | # Ref: https://wiki.debian.org/Multiarch/HOWTO 34 | # https://askubuntu.com/a/1336013/65814 35 | [ ! $(dpkg --get-selections | grep -q i386) ] && dpkg --remove-architecture i386 2>/dev/null 36 | 37 | # Fix apt ipv4/6 issue 38 | echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/1000-force-ipv4-transport 39 | 40 | export DEBIAN_FRONTEND=noninteractive 41 | # the following runs when apt cache is older than 6 hours 42 | if [ -z "$(find /var/cache/apt/pkgcache.bin -mmin -360 2> /dev/null)" ]; then 43 | printf '%-72s' "Updating apt cache" 44 | apt-get -qq update 45 | echo done. 46 | fi 47 | 48 | echo -------------------------- Prerequisites ------------------------------------ 49 | required_packages="apt-transport-https \ 50 | curl \ 51 | dnsutils \ 52 | language-pack-en \ 53 | pwgen \ 54 | fail2ban \ 55 | python-is-python3 \ 56 | python3-venv \ 57 | software-properties-common \ 58 | sudo \ 59 | tzdata \ 60 | unzip \ 61 | wget" 62 | 63 | for package in $required_packages 64 | do 65 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 66 | then 67 | # echo "'$package' is already installed" 68 | : 69 | else 70 | printf '%-72s' "Installing '${package}' ..." 71 | apt-get -qq install $package &> /dev/null 72 | 73 | # fix for apt refresh on first run. 74 | if [ "$?" -ne 0 ]; then 75 | apt-get update &> /dev/null 76 | apt-get -qq install $package &> /dev/null 77 | check_result $? "Couldn't install $package." 78 | fi 79 | 80 | echo done. 81 | fi 82 | done 83 | 84 | #--- setup timezone ---# 85 | current_time_zone=$(date +\%Z) 86 | if [ "$current_time_zone" != "UTC" ] ; then 87 | printf '%-72s' "Setting up timezone..." 88 | ln -fs /usr/share/zoneinfo/UTC /etc/localtime 89 | dpkg-reconfigure -f noninteractive tzdata 90 | # timedatectl set-timezone UTC 91 | check_result $? 'Error setting up timezone.' 92 | systemctl restart cron 93 | check_result $? 'Error restarting cron daemon.' 94 | echo done. 95 | fi 96 | 97 | echo ---------------------------------- LEMP ------------------------------------- 98 | 99 | php_ver=7.4 100 | lemp_packages="nginx-extras \ 101 | default-mysql-server \ 102 | php${php_ver}-fpm \ 103 | php${php_ver}-mysql \ 104 | php${php_ver}-gd \ 105 | php${php_ver}-cli \ 106 | php${php_ver}-xml \ 107 | php${php_ver}-mbstring \ 108 | php${php_ver}-soap \ 109 | php${php_ver}-curl \ 110 | php${php_ver}-zip \ 111 | php${php_ver}-bcmath \ 112 | php${php_ver}-intl \ 113 | php${php_ver}-imagick" 114 | 115 | for package in $lemp_packages 116 | do 117 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 118 | then 119 | echo "'$package' is already installed" 120 | : 121 | else 122 | # Remove ${php_ver} from package name to find if php-package is installed. 123 | php_package=$(printf '%s' "$package" | sed 's/[.0-9]*//g') 124 | if dpkg-query -W -f='${Status}' $php_package 2>/dev/null | grep -q "ok installed" 125 | then 126 | echo "'$package' is already installed as $php_package" 127 | : 128 | else 129 | printf '%-72s' "Installing '${package}' ..." 130 | apt-get -qq install $package &> /dev/null 131 | check_result $? "Error installing ${package}." 132 | echo done. 133 | fi 134 | fi 135 | done 136 | 137 | [ ! -d ~/git/wordpress-nginx ] && { 138 | git clone -q https://github.com/pothi/wordpress-nginx ~/git/wordpress-nginx 139 | cp -a ~/git/wordpress-nginx/{conf.d,errors,globals,sites-available} /etc/nginx/ 140 | [ ! -d /etc/nginx/sites-enabled ] && mkdir /etc/nginx/sites-enabled 141 | ln -fs /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/default.conf 142 | } 143 | 144 | # create dhparam 145 | if [ ! -f /etc/nginx/dhparam.pem ]; then 146 | $(which openssl) dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096 &> /dev/null 147 | sed -i 's:^# \(ssl_dhparam /etc/nginx/dhparam.pem;\)$:\1:' /etc/nginx/conf.d/ssl-common.conf 148 | fi 149 | 150 | echo ----------------------------------------------------------------------------- 151 | echo "Please check ~/.envrc for all the credentials." 152 | echo ----------------------------------------------------------------------------- 153 | 154 | if [ "$ADMIN_USER" == "" ]; then 155 | printf '%-72s' "Creating a MySQL Admin User..." 156 | # create MYSQL username automatically 157 | ADMIN_USER="admin_$(pwgen -Av 6 1)" 158 | ADMIN_PASS=$(pwgen -cnsv 20 1) 159 | echo "export ADMIN_USER=$ADMIN_USER" >> /root/.envrc 160 | echo "export ADMIN_PASS=$ADMIN_PASS" >> /root/.envrc 161 | mysql -e "CREATE USER ${ADMIN_USER} IDENTIFIED BY '${ADMIN_PASS}';" 162 | mysql -e "GRANT ALL PRIVILEGES ON *.* TO ${ADMIN_USER} WITH GRANT OPTION" 163 | echo done. 164 | echo "Please check ~/.envrc for credentials." 165 | fi 166 | 167 | wp_user=${WP_USERNAME:-""} 168 | if [ "$wp_user" == "" ]; then 169 | printf '%-72s' "Creating a WP User..." 170 | wp_user="wp_$(pwgen -Av 9 1)" 171 | echo "export WP_USERNAME=$wp_user" >> /root/.envrc 172 | echo done. 173 | fi 174 | 175 | # home_basename=$(echo $wp_user | awk -F _ '{print $1}') 176 | # [ -z $home_basename ] && home_basename=web 177 | home_basename=web 178 | 179 | if [ ! -d "/home/${home_basename}" ]; then 180 | useradd --shell=/bin/bash -m --home-dir /home/${home_basename} $wp_user 181 | 182 | groupadd ${home_basename} 183 | gpasswd -a $wp_user ${home_basename} &> /dev/null 184 | fi 185 | 186 | wp_pass=${WP_PASSWORD:-""} 187 | if [ "$wp_pass" == "" ]; then 188 | printf '%-72s' "Creating password for WP user..." 189 | wp_pass=$(pwgen -cns 12 1) 190 | echo "export WP_PASSWORD=$wp_pass" >> /root/.envrc 191 | 192 | echo "$wp_user:$wp_pass" | chpasswd 193 | echo done. 194 | fi 195 | 196 | # provide sudo access without passwd to WP Dev 197 | if [ ! -f /etc/sudoers.d/$wp_user ]; then 198 | printf '%-72s' "Providing sudo privilege for WP user..." 199 | echo "${wp_user} ALL=(ALL) NOPASSWD:ALL"> /etc/sudoers.d/$wp_user 200 | chmod 400 /etc/sudoers.d/$wp_user 201 | echo done. 202 | fi 203 | 204 | cd /etc/ssh/sshd_config.d 205 | if [ ! -f enable-passwd-auth.conf ]; then 206 | printf '%-72s' "Enabling Password Authentication for WP user..." 207 | echo "PasswordAuthentication yes" > enable-passwd-auth.conf 208 | /usr/sbin/sshd -t && systemctl restart sshd 209 | check_result $? 'Error restarting SSH daemon while enabling passwd auth.' 210 | echo done. 211 | fi 212 | cd - 1> /dev/null 213 | 214 | # . $local_wp_in_a_box_repo/scripts/php-installation.sh 215 | php_user=$wp_user 216 | fpm_ini_file=/etc/php/${php_ver}/fpm/php.ini 217 | pool_file=/etc/php/${php_ver}/fpm/pool.d/${php_user}.conf 218 | PM_METHOD=ondemand 219 | 220 | user_mem_limit=${PHP_MEM_LIMIT:-""} 221 | [ -z "$user_mem_limit" ] && user_mem_limit=256 222 | 223 | max_children=${PHP_MAX_CHILDREN:-""} 224 | 225 | if [ -z "$max_children" ]; then 226 | # let's be safe with a minmal value 227 | sys_memory=$(free -m | grep -oP '\d+' | head -n 1) 228 | if (($sys_memory <= 600)) ; then 229 | max_children=4 230 | elif (($sys_memory <= 1600)) ; then 231 | max_children=6 232 | elif (($sys_memory <= 5600)) ; then 233 | max_children=10 234 | elif (($sys_memory <= 10600)) ; then 235 | PM_METHOD=static 236 | max_children=20 237 | elif (($sys_memory <= 20600)) ; then 238 | PM_METHOD=static 239 | max_children=40 240 | elif (($sys_memory <= 30600)) ; then 241 | PM_METHOD=static 242 | max_children=60 243 | elif (($sys_memory <= 40600)) ; then 244 | PM_METHOD=static 245 | max_children=80 246 | else 247 | PM_METHOD=static 248 | max_children=100 249 | fi 250 | fi 251 | 252 | env_type=${ENV_TYPE:-""} 253 | if [[ $env_type = "local" ]]; then 254 | PM_METHOD=ondemand 255 | fi 256 | 257 | echo "Configuring memory limit to ${user_mem_limit}MB" 258 | sed -i -e '/^memory_limit/ s/=.*/= '$user_mem_limit'M/' $fpm_ini_file 259 | 260 | user_max_filesize=${PHP_MAX_FILESIZE:-64} 261 | echo "Configuring 'post_max_size' and 'upload_max_filesize' to ${user_max_filesize}MB..." 262 | sed -i -e '/^post_max_size/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 263 | sed -i -e '/^upload_max_filesize/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 264 | 265 | user_max_input_vars=${PHP_MAX_INPUT_VARS:-5000} 266 | echo "Configuring 'max_input_vars' to $user_max_input_vars (from the default 1000)..." 267 | sed -i '/max_input_vars/ s/;\? \?\(max_input_vars \?= \?\)[[:digit:]]\+/\1'$user_max_input_vars'/' $fpm_ini_file 268 | 269 | # Setup timezone 270 | user_timezone=${USER_TIMEZONE:-UTC} 271 | echo "Configuring timezone to $user_timezone ..." 272 | sed -i -e 's/^;date\.timezone =$/date.timezone = "'$user_timezone'"/' $fpm_ini_file 273 | export PHP_PCNTL_FUNCTIONS='pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare' 274 | export PHP_EXEC_FUNCTIONS='escapeshellarg,escapeshellcmd,exec,passthru,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,shell_exec,system' 275 | sed -i "/disable_functions/c disable_functions = ${PHP_PCNTL_FUNCTIONS},${PHP_EXEC_FUNCTIONS}" $fpm_ini_file 276 | 277 | [ ! -f $pool_file ] && cp /etc/php/${php_ver}/fpm/pool.d/www.conf $pool_file 278 | sed -i -e 's/^\[www\]$/['$php_user']/' $pool_file 279 | sed -i -e 's/www-data/'$php_user'/' $pool_file 280 | sed -i -e '/^;listen.\(owner\|group\|mode\)/ s/^;//' $pool_file 281 | sed -i -e '/^listen.mode = / s/[0-9]\{4\}/0666/' $pool_file 282 | 283 | php_ver_short=$(echo $php_ver | sed 's/\.//') 284 | socket=/run/php/fpm-${php_ver_short}-${php_user}.sock 285 | sed -i "/^listen =/ s:=.*:= $socket:" $pool_file 286 | [ -f /etc/nginx/conf.d/lb.conf ] && sed -i "s:/var/lock/php-fpm.*;:$socket;:" /etc/nginx/conf.d/lb.conf 287 | if [ ! -f /etc/nginx/conf.d/fpm${php_ver_short}.conf ]; then 288 | echo "upstream fpm${php_ver_short} { server unix:$socket; }" > /etc/nginx/conf.d/fpm${php_ver_short}.conf 289 | echo "upstream fpm { server unix:$socket; }" > /etc/nginx/conf.d/fpm.conf 290 | [ -f /etc/nginx/conf.d/lb.conf ] && rm /etc/nginx/conf.d/lb.conf 291 | fi 292 | 293 | sed -i -e 's/^pm = .*/pm = '$PM_METHOD'/' $pool_file 294 | sed -i '/^pm.max_children/ s/=.*/= '$max_children'/' $pool_file 295 | 296 | PHP_MIN=$(expr $max_children / 10) 297 | 298 | sed -i '/^;catch_workers_output/ s/^;//' $pool_file 299 | sed -i '/^;pm.process_idle_timeout/ s/^;//' $pool_file 300 | sed -i '/^;pm.max_requests/ s/^;//' $pool_file 301 | sed -i '/^;pm.status_path/ s/^;//' $pool_file 302 | sed -i '/^;ping.path/ s/^;//' $pool_file 303 | sed -i '/^;ping.response/ s/^;//' $pool_file 304 | 305 | # home_basename=web 306 | # home_basename=$(echo $wp_user | awk -F _ '{print $1}') 307 | # [ -z $home_basename ] && home_basename=web 308 | # [ ! -d /home/${home_basename}/log ] && mkdir /home/${home_basename}/log 309 | PHP_SLOW_LOG_PATH="/var/log/slow-php.log" 310 | sed -i '/^;slowlog/ s/^;//' $pool_file 311 | sed -i '/^slowlog/ s:=.*$: = '$PHP_SLOW_LOG_PATH':' $pool_file 312 | sed -i '/^;request_slowlog_timeout/ s/^;//' $pool_file 313 | sed -i '/^request_slowlog_timeout/ s/= .*$/= 60/' $pool_file 314 | 315 | FPMCONF="/etc/php/${php_ver}/fpm/php-fpm.conf" 316 | sed -i '/^;emergency_restart_threshold/ s/^;//' $FPMCONF 317 | sed -i '/^emergency_restart_threshold/ s/=.*$/= '$PHP_MIN'/' $FPMCONF 318 | sed -i '/^;emergency_restart_interval/ s/^;//' $FPMCONF 319 | sed -i '/^emergency_restart_interval/ s/=.*$/= 1m/' $FPMCONF 320 | sed -i '/^;process_control_timeout/ s/^;//' $FPMCONF 321 | sed -i '/^process_control_timeout/ s/=.*$/= 10s/' $FPMCONF 322 | 323 | # restart php upon OOM or other failures 324 | # ref: https://stackoverflow.com/a/45107512/1004587 325 | # TODO: Do the following only if "Restart=on-failure" is not found in that file. 326 | sed -i '/^\[Service\]/!b;:a;n;/./ba;iRestart=on-failure' /lib/systemd/system/php${php_ver}-fpm.service 327 | systemctl daemon-reload 328 | check_result $? "Could not update /lib/systemd/system/php${php_ver}-fpm.service file!" 329 | 330 | printf '%-72s' "Restarting PHP-FPM..." 331 | /usr/sbin/php-fpm${php_ver} -t 2>/dev/null && systemctl restart php${php_ver}-fpm 332 | echo done. 333 | 334 | printf '%-72s' "Restarting Nginx..." 335 | /usr/sbin/nginx -t 2>/dev/null && systemctl restart nginx 336 | echo done. 337 | 338 | echo --------------------------- Certbot ----------------------------------------- 339 | snap install core 340 | snap refresh core 341 | apt-get -qq remove certbot 342 | snap install --classic certbot 343 | ln -fs /snap/bin/certbot /usr/bin/certbot 344 | 345 | [ ! -d /etc/letsencrypt/renewal-hooks/deploy/ ] && mkdir -p /etc/letsencrypt/renewal-hooks/deploy/ 346 | restart_script=/etc/letsencrypt/renewal-hooks/deploy/nginx-restart.sh 347 | restart_script_url=https://github.com/pothi/snippets/raw/main/ssl/nginx-restart.sh 348 | [ ! -f "$restart_script" ] && { 349 | wget -q -O $restart_script $restart_script_url 350 | check_result $? "Error downloading Nginx Restart Script for Certbot renewals." 351 | chmod +x $restart_script 352 | } 353 | 354 | echo All done. 355 | 356 | echo ----------------------------------------------------------------------------- 357 | echo You may find the login credentials of SFTP/SSH user in /root/.envrc file. 358 | echo ----------------------------------------------------------------------------- 359 | 360 | echo 'You may reboot only once to apply certain updates (ex: kernel updates)!' 361 | echo 362 | 363 | echo "Script ended on (date & time): $(date +%c)" 364 | echo 365 | -------------------------------------------------------------------------------- /bootstrap-ubuntu-noble.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # Version: 2.0 7 | 8 | short_name=noble 9 | 10 | # this is the PHP version that comes by default with the current Ubuntu LTS 11 | php_ver=8.3 12 | 13 | # to be run as root, probably as a user-script just after a server is installed 14 | # https://stackoverflow.com/a/52586842/1004587 15 | # also see https://stackoverflow.com/q/3522341/1004587 16 | is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; } 17 | [ is_user_root ] || { echo 'You must be root or have sudo privilege to run this script. Exiting now.'; exit 1; } 18 | 19 | export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin 20 | export DEBIAN_FRONTEND=noninteractive 21 | 22 | [ -d ~/backups ] || mkdir ~/backups 23 | [ -d ~/log ] || mkdir ~/log 24 | 25 | # logging everything 26 | log_file=${HOME}/log/wp-in-a-box-${short_name}.log 27 | exec > >(tee -a ${log_file} ) 28 | exec 2> >(tee -a ${log_file} >&2) 29 | 30 | echo "Script started on (date & time): $(date +%c)" 31 | 32 | #--- Useful functions ---# 33 | 34 | # helper function to exit upon non-zero exit code of a command 35 | # usage some_command; check_result $? 'some_command failed' 36 | if ! $(type 'check_result' 2>/dev/null | grep -q 'function') ; then 37 | check_result() { 38 | if [ "$1" -ne 0 ]; then 39 | echo -e "\nError: $2. Exiting!\n" 40 | exit "$1" 41 | fi 42 | } 43 | fi 44 | 45 | # function to configure timezone to UTC 46 | set_utc_timezone() { 47 | if [ "$(date +\%Z)" != "UTC" ] ; then 48 | [ ! -f /usr/sbin/tzconfig ] && apt-get -qq install tzdata > /dev/null 49 | printf '%-72s' "Setting up timezone..." 50 | ln -fs /usr/share/zoneinfo/UTC /etc/localtime 51 | dpkg-reconfigure -f noninteractive tzdata 52 | # timedatectl set-timezone UTC 53 | check_result $? 'Error setting up timezone.' 54 | 55 | # Recommended to restart cron after every change in timezone 56 | systemctl restart cron 57 | check_result $? 'Error restarting cron daemon after changing timezone.' 58 | echo done. 59 | fi 60 | } 61 | 62 | if ! $(type 'install_package' 2>/dev/null | grep -q 'function') ; then 63 | install_package() { 64 | package=$1 65 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 66 | then 67 | # echo "'$package' is already installed" 68 | : 69 | else 70 | printf '%-72s' "Installing '${package}' ..." 71 | apt-get -qq install $package > /dev/null 72 | check_result $? "Couldn't install $package." 73 | echo done. 74 | fi 75 | } 76 | fi 77 | 78 | #--- end of Useful functions ---# 79 | 80 | # if ~/.envrc doesn't exist, create it 81 | if [ ! -f "$HOME/.envrc" ]; then 82 | touch ~/.envrc 83 | chmod 600 ~/.envrc 84 | # if exists, source it to apply the env variables 85 | else 86 | . ~/.envrc 87 | fi 88 | 89 | [ -z "$PHP_VERSION" ] && echo "export PHP_VERSION=$php_ver" >> /root/.envrc 90 | 91 | #--- swap ---# 92 | if free | awk '/^Swap:/ {exit !$2}'; then 93 | # echo 'Swap already exists!' 94 | : 95 | else 96 | printf '%-72s' "Creating swap..." 97 | wget -O /tmp/swap.sh -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/swap.sh 98 | bash /tmp/swap.sh >/dev/null 99 | rm /tmp/swap.sh 100 | echo done. 101 | fi 102 | 103 | #--- apt tweaks ---# 104 | 105 | # Ref: https://wiki.debian.org/Multiarch/HOWTO 106 | # https://askubuntu.com/a/1336013/65814 107 | [ ! $(dpkg --get-selections | grep -q i386) ] && dpkg --remove-architecture i386 2>/dev/null 108 | 109 | # Fix apt ipv4/6 issue 110 | [ ! -f /etc/apt/apt.conf.d/1000-force-ipv4-transport ] && \ 111 | echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/1000-force-ipv4-transport 112 | 113 | # Fix a warning related to dialog 114 | # run `debconf-show debconf` to see the current /default selections. 115 | echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 116 | 117 | # the following runs when apt cache is older than 6 hours 118 | # Taken from Ansible - https://askubuntu.com/a/1362550/65814 119 | APT_UPDATE_SUCCESS_STAMP_PATH=/var/lib/apt/periodic/update-success-stamp 120 | APT_LISTS_PATH=/var/lib/apt/lists 121 | if [ -f "$APT_UPDATE_SUCCESS_STAMP_PATH" ]; then 122 | if [ -z "$(find "$APT_UPDATE_SUCCESS_STAMP_PATH" -mmin -360 2> /dev/null)" ]; then 123 | printf '%-72s' "Updating apt cache" 124 | apt-get -qq update 125 | echo done. 126 | fi 127 | elif [ -d "$APT_LISTS_PATH" ]; then 128 | if [ -z "$(find "$APT_LISTS_PATH" -mmin -360 2> /dev/null)" ]; then 129 | printf '%-72s' "Updating apt cache" 130 | apt-get -qq update 131 | echo done. 132 | fi 133 | fi 134 | 135 | # ref: https://askubuntu.com/q/114759/65814 (use any one solution - the accepted answer and the other) 136 | # ref: https://www.server-world.info/en/note?os=Debian_10&p=locale - once you installed locales-all, you can not use the accepted solution from askubuntu. 137 | lang=$LANG 138 | if [ "$lang" != "en_US.UTF-8" ]; then 139 | if dpkg-query -W -f='${Status}' locales 2>/dev/null | grep -q "ok installed" ; then : 140 | else 141 | printf '%-72s' "Installing locale..." 142 | apt-get -qq install locales 143 | echo done. 144 | fi 145 | # localectl set-locale LANG=en_US.UTF-8 146 | locale-gen en_US.UTF-8 >/dev/null 147 | update-locale LANG=en_US.UTF-8 148 | source /etc/default/locale 149 | fi 150 | 151 | # -------------------------- Prerequisites ------------------------------------ 152 | 153 | # apt-utils to fix an annoying non-critical bug on minimal images. Ref: https://github.com/tianon/docker-brew-ubuntu-core/issues/59 154 | install_package apt-utils 155 | 156 | # powermgmt-base to fix a warning in unattended-upgrade.log 157 | required_packages="curl \ 158 | dnsutils \ 159 | fail2ban \ 160 | git \ 161 | memcached \ 162 | powermgmt-base \ 163 | software-properties-common \ 164 | sudo \ 165 | unzip \ 166 | wget" 167 | 168 | for package in $required_packages 169 | do 170 | install_package $package 171 | done 172 | 173 | # MySQL is required by PHP. 174 | install_package default-mysql-server 175 | 176 | # PHP is required by Nginx to configure the defaults. 177 | php_packages="php${php_ver}-common \ 178 | php${php_ver}-mysql \ 179 | php${php_ver}-gd \ 180 | php${php_ver}-cli \ 181 | php${php_ver}-xml \ 182 | php${php_ver}-mbstring \ 183 | php${php_ver}-soap \ 184 | php${php_ver}-curl \ 185 | php${php_ver}-zip \ 186 | php${php_ver}-bcmath \ 187 | php${php_ver}-intl \ 188 | php${php_ver}-imagick \ 189 | php${php_ver}-memcache \ 190 | php${php_ver}-memcached \ 191 | php${php_ver}-fpm" 192 | 193 | # package=php${php_ver}-fpm 194 | for package in $php_packages 195 | do 196 | install_package $package 197 | done 198 | 199 | # nginx 200 | install_package nginx-extras 201 | 202 | # fail2ban needs to be started manually after installation. 203 | systemctl start fail2ban 204 | 205 | # configure some defaults for git and etckeeper 206 | git config --global user.name "root" 207 | git config --global user.email "root@localhost" 208 | git config --global init.defaultBranch main 209 | 210 | #--- setup timezone ---# 211 | set_utc_timezone 212 | 213 | # initial backup of /etc 214 | [ -d ~/backup/etc-init ] || cp -a /etc ~/backups/etc-init 215 | 216 | # Create a WordPress user with /home/wp as $HOME 217 | wp_user=${WP_USERNAME:-""} 218 | if [ "$wp_user" == "" ]; then 219 | printf '%-72s' "Creating a WP User..." 220 | wp_user="wp_$(openssl rand -base64 32 | tr -d /=+ | cut -c -10)" 221 | echo "export WP_USERNAME=$wp_user" >> /root/.envrc 222 | echo done. 223 | fi 224 | 225 | # home_basename=$(echo $wp_user | awk -F _ '{print $1}') 226 | # [ -z $home_basename ] && home_basename=web 227 | home_basename=wp 228 | 229 | useradd --shell=/bin/bash -m --home-dir /home/${home_basename} $wp_user 230 | chmod 755 /home/$home_basename 231 | 232 | groupadd ${home_basename} 233 | gpasswd -a $wp_user ${home_basename} > /dev/null 234 | 235 | # Create password for WP User 236 | wp_pass=${WP_PASSWORD:-""} 237 | if [ "$wp_pass" == "" ]; then 238 | printf '%-72s' "Creating password for WP user..." 239 | wp_pass=$(openssl rand -base64 32 | tr -d /=+ | cut -c -20) 240 | echo "export WP_PASSWORD=$wp_pass" >> /root/.envrc 241 | echo done. 242 | fi 243 | 244 | echo "$wp_user:$wp_pass" | chpasswd 245 | 246 | # provide sudo access without passwd to WP User 247 | if [ ! -f /etc/sudoers.d/$wp_user ]; then 248 | printf '%-72s' "Providing sudo privilege for WP user..." 249 | echo "${wp_user} ALL=(ALL) NOPASSWD:ALL"> /etc/sudoers.d/$wp_user 250 | chmod 400 /etc/sudoers.d/$wp_user 251 | echo done. 252 | fi 253 | 254 | # Enable password authentication for WP User 255 | cd /etc/ssh/sshd_config.d 256 | if [ ! -f enable-passwd-auth.conf ]; then 257 | printf '%-72s' "Enabling Password Authentication for WP user..." 258 | echo "PasswordAuthentication yes" > enable-passwd-auth.conf 259 | /usr/sbin/sshd -t && systemctl restart ssh 260 | check_result $? 'Error restarting SSH daemon while enabling passwd auth.' 261 | echo done. 262 | fi 263 | if [ ! -f rsa.conf ]; then 264 | printf '%-72s' "Enabling RSA SSH support" 265 | echo "PubkeyAcceptedAlgorithms +ssh-rsa" > rsa.conf 266 | /usr/sbin/sshd -t && systemctl restart ssh 267 | check_result $? 'Failed to incorporate /etc/ssh/sshd_config.d/rsa.conf' 268 | echo done. 269 | fi 270 | cd - 1> /dev/null 271 | 272 | echo ---------------------------------- LEMP ------------------------------------- 273 | 274 | # ------------------------------- MySQL --------------------------------------- 275 | 276 | # Create a MySQL admin user 277 | sql_user=${MYSQL_ADMIN_USER:-""} 278 | if [ "$sql_user" == "" ]; then 279 | printf '%-72s' "Creating a MySQL Admin User..." 280 | # create MYSQL username automatically 281 | # unique username / password generator: https://unix.stackexchange.com/q/230673/20241 282 | sql_user="mysql_$(openssl rand -base64 32 | tr -d /=+ | cut -c -10)" 283 | echo "export MYSQL_ADMIN_USER=$sql_user" >> /root/.envrc 284 | echo done. 285 | fi 286 | 287 | sql_pass=${MYSQL_ADMIN_PASS:-""} 288 | if [ "$sql_pass" == "" ]; then 289 | sql_pass=$(openssl rand -base64 32 | tr -d /=+ | cut -c -20) 290 | echo "export MYSQL_ADMIN_PASS=$sql_pass" >> /root/.envrc 291 | fi 292 | 293 | mysql -e "CREATE USER IF NOT EXISTS ${sql_user} IDENTIFIED BY '${sql_pass}';" 294 | mysql -e "GRANT ALL PRIVILEGES ON *.* TO ${sql_user} WITH GRANT OPTION" 295 | 296 | # echo -------------------------------- PHP ---------------------------------------- 297 | echo -------------------------------- Configuring PHP ---------------------------------------- 298 | 299 | php_user=$wp_user 300 | fpm_ini_file=/etc/php/${php_ver}/fpm/php.ini 301 | pool_file=/etc/php/${php_ver}/fpm/pool.d/${php_user}.conf 302 | default_pool_file=/etc/php/${php_ver}/fpm/pool.d/www.conf 303 | PM_METHOD=ondemand 304 | 305 | user_mem_limit=${PHP_MEM_LIMIT:-""} 306 | [ -z "$user_mem_limit" ] && user_mem_limit=512 307 | 308 | max_children=${PHP_MAX_CHILDREN:-""} 309 | 310 | if [ -z "$max_children" ]; then 311 | # let's be safe with a minmal value 312 | sys_memory=$(free -m | grep -oP '\d+' | head -n 1) 313 | if (($sys_memory <= 600)) ; then 314 | max_children=4 315 | elif (($sys_memory <= 1600)) ; then 316 | max_children=6 317 | elif (($sys_memory <= 5600)) ; then 318 | max_children=10 319 | elif (($sys_memory <= 10600)) ; then 320 | PM_METHOD=static 321 | max_children=20 322 | else 323 | PM_METHOD=static 324 | max_children=50 325 | fi 326 | fi 327 | 328 | env_type=${ENV_TYPE:-""} 329 | if [[ $env_type = "local" ]]; then 330 | PM_METHOD=ondemand 331 | fi 332 | 333 | echo "Configuring memory limit to ${user_mem_limit}MB" 334 | sed -i -e '/^memory_limit/ s/=.*/= '$user_mem_limit'M/' $fpm_ini_file 335 | 336 | user_max_filesize=${PHP_MAX_FILESIZE:-64} 337 | echo "Configuring 'post_max_size' and 'upload_max_filesize' to ${user_max_filesize}MB..." 338 | sed -i -e '/^post_max_size/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 339 | sed -i -e '/^upload_max_filesize/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 340 | 341 | user_max_input_vars=${PHP_MAX_INPUT_VARS:-5000} 342 | echo "Configuring 'max_input_vars' to $user_max_input_vars (from the default 1000)..." 343 | sed -i '/max_input_vars/ s/;\? \?\(max_input_vars \?= \?\)[[:digit:]]\+/\1'$user_max_input_vars'/' $fpm_ini_file 344 | 345 | # Setup timezone 346 | user_timezone=${USER_TIMEZONE:-UTC} 347 | echo "Configuring timezone to $user_timezone ..." 348 | sed -i -e 's/^;date\.timezone =$/date.timezone = "'$user_timezone'"/' $fpm_ini_file 349 | export PHP_PCNTL_FUNCTIONS='pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare' 350 | export PHP_EXEC_FUNCTIONS='escapeshellarg,escapeshellcmd,exec,passthru,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,shell_exec,system' 351 | sed -i "/disable_functions/c disable_functions = ${PHP_PCNTL_FUNCTIONS},${PHP_EXEC_FUNCTIONS}" $fpm_ini_file 352 | 353 | [ ! -f $pool_file ] && cp /etc/php/${php_ver}/fpm/pool.d/www.conf $pool_file 354 | [ -f /etc/php/${php_ver}/fpm/pool.d/www.conf ] && mv /etc/php/${php_ver}/fpm/pool.d/www.conf ~/backups/php-www.conf-$(date +%F) 355 | sed -i -e 's/^\[www\]$/['$php_user']/' $pool_file 356 | sed -i -e 's/www-data/'$php_user'/' $pool_file 357 | sed -i -e '/^;listen.\(owner\|group\|mode\)/ s/^;//' $pool_file 358 | sed -i -e '/^listen.mode = / s/[0-9]\{4\}/0666/' $pool_file 359 | 360 | sed -i -e 's/^pm = .*/pm = '$PM_METHOD'/' $pool_file 361 | sed -i '/^pm.max_children/ s/=.*/= '$max_children'/' $pool_file 362 | 363 | PHP_MIN=$(expr $max_children / 10) 364 | 365 | sed -i '/^;catch_workers_output/ s/^;//' $pool_file 366 | sed -i '/^;pm.process_idle_timeout/ s/^;//' $pool_file 367 | sed -i '/^;pm.max_requests/ s/^;//' $pool_file 368 | sed -i '/^;pm.status_path/ s/^;//' $pool_file 369 | sed -i '/^;ping.path/ s/^;//' $pool_file 370 | sed -i '/^;ping.response/ s/^;//' $pool_file 371 | 372 | # home_basename=web 373 | # home_basename=$(echo $wp_user | awk -F _ '{print $1}') 374 | # [ -z $home_basename ] && home_basename=web 375 | # [ ! -d /home/${home_basename}/log ] && mkdir /home/${home_basename}/log 376 | PHP_SLOW_LOG_PATH="/var/log/slow-php.log" 377 | sed -i '/^;slowlog/ s/^;//' $pool_file 378 | sed -i '/^slowlog/ s:=.*$: = '$PHP_SLOW_LOG_PATH':' $pool_file 379 | sed -i '/^;request_slowlog_timeout/ s/^;//' $pool_file 380 | sed -i '/^request_slowlog_timeout/ s/= .*$/= 60/' $pool_file 381 | 382 | FPMCONF="/etc/php/${php_ver}/fpm/php-fpm.conf" 383 | sed -i '/^;emergency_restart_threshold/ s/^;//' $FPMCONF 384 | sed -i '/^emergency_restart_threshold/ s/=.*$/= '$PHP_MIN'/' $FPMCONF 385 | sed -i '/^;emergency_restart_interval/ s/^;//' $FPMCONF 386 | sed -i '/^emergency_restart_interval/ s/=.*$/= 1m/' $FPMCONF 387 | sed -i '/^;process_control_timeout/ s/^;//' $FPMCONF 388 | sed -i '/^process_control_timeout/ s/=.*$/= 10s/' $FPMCONF 389 | 390 | # restart php upon OOM or other failures 391 | # ref: https://stackoverflow.com/a/45107512/1004587 392 | # TODO: Do the following only if "Restart=on-failure" is not found in that file. 393 | sed -i '/^\[Service\]/!b;:a;n;/./ba;iRestart=on-failure' /lib/systemd/system/php${php_ver}-fpm.service 394 | systemctl daemon-reload 395 | check_result $? "Could not update /lib/systemd/system/php${php_ver}-fpm.service file!" 396 | 397 | printf '%-72s' "Restarting PHP-FPM..." 398 | /usr/sbin/php-fpm${php_ver} -t 2>/dev/null && systemctl restart php${php_ver}-fpm 399 | echo done. 400 | 401 | # echo -------------------------------- Nginx ---------------------------------------- 402 | 403 | # Download WordPress Nginx repo 404 | [ ! -d ~/wp-nginx ] && { 405 | mkdir ~/wp-nginx 406 | wget -q -O- https://github.com/pothi/wordpress-nginx/tarball/main | tar -xz -C ~/wp-nginx --strip-components=1 407 | cp -a ~/wp-nginx/{conf.d,errors,globals,sites-available} /etc/nginx/ 408 | [ ! -d /etc/nginx/sites-enabled ] && mkdir /etc/nginx/sites-enabled 409 | ln -fs /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/default.conf 410 | } 411 | 412 | # Remove the default conf file supplied by OS 413 | [ -f /etc/nginx/sites-enabled/default ] && rm /etc/nginx/sites-enabled/default 414 | 415 | # Remove the default SSL conf to support latest SSL conf. 416 | # It should hide two lines starting with ssl_ 417 | # ^ starting with... 418 | # \s* matches any number of space or tab elements before ssl_ 419 | # when run more than once, it just doesn't do anything as the start of the line is '#' after the first execution. 420 | sed -i 's/^\s*ssl_/# &/' /etc/nginx/nginx.conf 421 | 422 | # create dhparam 423 | if [ ! -f /etc/nginx/dhparam.pem ]; then 424 | openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096 &> /dev/null 425 | sed -i 's:^# \(ssl_dhparam /etc/nginx/dhparam.pem;\)$:\1:' /etc/nginx/conf.d/ssl-common.conf 426 | fi 427 | 428 | # if php_ver is 6.0 then php_ver_short is 60 429 | php_ver_short=$(echo $php_ver | sed 's/\.//') 430 | socket=/run/php/fpm-${php_ver_short}-${php_user}.sock 431 | sed -i "/^listen =/ s:=.*:= $socket:" $pool_file 432 | # [ -f /etc/nginx/conf.d/lb.conf ] && sed -i "s:/var/lock/php-fpm.*;:$socket;:" /etc/nginx/conf.d/lb.conf 433 | [ -f /etc/nginx/conf.d/lb.conf ] && rm /etc/nginx/conf.d/lb.conf 434 | [ ! -f /etc/nginx/conf.d/fpm.conf ] && echo "upstream fpm { server unix:$socket; }" > /etc/nginx/conf.d/fpm.conf 435 | [ ! -f /etc/nginx/conf.d/fpm${php_ver_short}.conf ] && echo "upstream fpm${php_ver_short} { server unix:$socket; }" > /etc/nginx/conf.d/fpm${php_ver_short}.conf 436 | 437 | printf '%-72s' "Restarting Nginx..." 438 | /usr/sbin/nginx -t 2>/dev/null && systemctl restart nginx 439 | echo done. 440 | 441 | echo --------------------------- Certbot ----------------------------------------- 442 | snap install core 443 | snap refresh core 444 | apt-get -qq remove certbot 445 | snap install --classic certbot 446 | ln -fs /snap/bin/certbot /usr/bin/certbot 447 | 448 | # register certbot account if email is supplied 449 | if [ $CERTBOT_ADMIN_EMAIL ]; then 450 | certbot show_account &> /dev/null 451 | if [ "$?" != "0" ]; then 452 | certbot -m $CERTBOT_ADMIN_EMAIL --agree-tos --no-eff-email register 453 | fi 454 | fi 455 | 456 | # Restart script upon renewal; it can also alert upon success or failure 457 | # See - https://github.com/pothi/snippets/blob/main/ssl/nginx-restart.sh 458 | [ ! -d /etc/letsencrypt/renewal-hooks/deploy/ ] && mkdir -p /etc/letsencrypt/renewal-hooks/deploy/ 459 | restart_script=/etc/letsencrypt/renewal-hooks/deploy/nginx-restart.sh 460 | restart_script_url=https://github.com/pothi/snippets/raw/main/ssl/nginx-restart.sh 461 | [ ! -f "$restart_script" ] && { 462 | wget -q -O $restart_script $restart_script_url 463 | check_result $? "Could not download Nginx Restart Script for Certbot renewals." 464 | chmod +x $restart_script 465 | } 466 | 467 | [ -d ~/backup/etc-certbot-default ] || cp -a /etc ~/backups/etc-certbot-default 468 | 469 | #--- Additional Steps ---# 470 | # ~/.ssh tweaks 471 | if [ ! -d /home/${home_basename}/.ssh ]; then 472 | mkdir /home/${home_basename}/.ssh 473 | chmod 700 /home/${home_basename}/.ssh 474 | chown ${wp_user}:${wp_user} /home/${home_basename}/.ssh 475 | fi 476 | cp -a ~/.ssh/authorized_keys /home/${home_basename}/.ssh 477 | chown ${wp_user}:${wp_user} /home/${home_basename}/.ssh/* 478 | 479 | # bootstrap root user 480 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/bootstrap-root.sh 481 | bash bootstrap-root.sh && rm bootstrap-root.sh 482 | check_result $? "Could not bootstrap root." 483 | 484 | #-------------------- Install mta (postfix) --------------------# 485 | if ! command -v mail >/dev/null; then 486 | printf '%-72s' "Installing MTA (postfix)..." 487 | [ -f email-mta-installation.sh ] && rm email-mta-installation.sh 488 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/email-mta-installation.sh 489 | bash email-mta-installation.sh > /dev/null 490 | check_result $? "Could not install MTA(postfix)." 491 | [ -f email-mta-installation.sh ] && rm email-mta-installation.sh 492 | echo done. 493 | fi 494 | 495 | # bootstrap unattended upgrades and reboots 496 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/unattended-upgrades.sh 497 | bash unattended-upgrades.sh && rm unattended-upgrades.sh 498 | check_result $? "Could not bootstrap Unattended Upgrades script." 499 | wget -q https://github.com/pothi/wp-in-a-box/raw/main/scripts/unattended-reboots.sh 500 | bash unattended-reboots.sh && rm unattended-reboots.sh 501 | check_result $? "Could not bootstrap Unattended Reboot script." 502 | 503 | printf '%-72s' "Running apt upgrade... it may take sometime..." 504 | apt-get -qq upgrade > /dev/null 505 | check_result $? "Could not run 'apt upgrade'." 506 | echo done. 507 | 508 | echo All done. 509 | 510 | echo ----------------------------------------------------------------------------- 511 | echo You may find the login credentials of SFTP/SSH user in /root/.envrc file. 512 | echo ----------------------------------------------------------------------------- 513 | 514 | echo 'You may reboot (only) once to apply certain updates (ex: kernel updates)!' 515 | echo 516 | 517 | echo "Script ended on (date & time): $(date +%c)" 518 | echo 519 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # Version: 2.2 7 | 8 | # to be run as root, probably as a user-script just after a server is installed 9 | 10 | # as root 11 | # if [[ $USER != "root" ]]; then 12 | # echo "This script must be run as root" 13 | # exit 1 14 | # fi 15 | 16 | # ref: https://packages.sury.org/php/README.txt 17 | # if [ "$(whoami)" != "root" ]; then 18 | # SUDO=sudo 19 | # else 20 | # SUDO= 21 | # fi 22 | 23 | # https://stackoverflow.com/a/52586842/1004587 24 | # also see https://stackoverflow.com/q/3522341/1004587 25 | is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; } 26 | [ is_user_root ] || { echo 'You must be root or user with sudo privilege to run this script. Exiting now.'; exit 1; } 27 | 28 | # create some useful directories - create them on demand 29 | mkdir -p ${HOME}/{backups,git,log,scripts,tmp} &> /dev/null 30 | 31 | # logging everything 32 | log_file=${HOME}/log/wp-in-a-box.log 33 | exec > >(tee -a ${log_file} ) 34 | exec 2> >(tee -a ${log_file} >&2) 35 | 36 | echo "Script started on (date & time): $(date +%c)" 37 | 38 | # helper function to exit upon non-zero exit code of a command 39 | # usage some_command; check_result $? 'some_command failed' 40 | if ! $(type 'check_result' 2>/dev/null | grep -q 'function') ; then 41 | check_result() { 42 | if [ "$1" -ne 0 ]; then 43 | echo -e "\nError: $2. Exiting!\n" 44 | exit "$1" 45 | fi 46 | } 47 | fi 48 | 49 | if ! $(type 'codename' 2>/dev/null | grep -q 'function') 50 | then 51 | codename() { 52 | lsb_release_cli=$(which lsb_release) 53 | local codename="" 54 | if [ ! -z $lsb_release_cli ]; then 55 | codename=$($lsb_release_cli -cs) 56 | else 57 | codename=$(cat /etc/os-release | awk -F = '/VERSION_CODENAME/{print $2}') 58 | fi 59 | echo "$codename" 60 | } 61 | codename=$(codename) 62 | fi 63 | 64 | [ ! -f "$HOME/.envrc" ] && touch ~/.envrc 65 | 66 | EMAIL=root@localhost 67 | if ! grep -qw $EMAIL /root/.envrc ; then 68 | echo "export EMAIL=$EMAIL" >> /root/.envrc 69 | fi 70 | NAME='root' 71 | if ! grep -qw "$NAME" /root/.envrc ; then 72 | echo "export NAME='$NAME'" >> /root/.envrc 73 | fi 74 | 75 | local_wp_in_a_box_repo=/root/git/wp-in-a-box 76 | 77 | . /root/.envrc 78 | 79 | # take a backup 80 | backup_dir="/root/backups/etc-before-wp-in-a-box-$(date +%F)" 81 | if [ ! -d "$backup_dir" ]; then 82 | printf '%-72s' "Taking initial backup..." 83 | mkdir $backup_dir 84 | cp -a /etc $backup_dir 85 | echo done. 86 | fi 87 | 88 | # Ref: https://wiki.debian.org/Multiarch/HOWTO 89 | # https://askubuntu.com/a/1336013/65814 90 | [ ! $(dpkg --get-selections | grep -q i386) ] && dpkg --remove-architecture i386 2>/dev/null 91 | 92 | # Fix apt ipv4/6 issue 93 | echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/1000-force-ipv4-transport 94 | 95 | export DEBIAN_FRONTEND=noninteractive 96 | # the following runs when apt cache is older than an hour 97 | printf '%-72s' "Updating apt repos..." 98 | [ -z "$(find /var/cache/apt/pkgcache.bin -mmin -60 2> /dev/null)" ] && apt-get -qq update 99 | echo done. 100 | 101 | # Redirection explanation: https://unix.stackexchange.com/a/563563/20241 102 | printf '%-72s' "Installing git..." 103 | if ! dpkg-query -W -f='${Status}' git 2>/dev/null | grep -q "ok installed"; then apt-get -qq install git 1>/dev/null ; fi 104 | echo done. 105 | git config --global --replace-all user.email "$EMAIL" 106 | git config --global --replace-all user.name "$NAME" 107 | 108 | printf '%-72s' "Fetching wp-in-a-box repo..." 109 | if [ ! -d $local_wp_in_a_box_repo ] ; then 110 | git clone -q --recursive https://github.com/pothi/wp-in-a-box $local_wp_in_a_box_repo &> /dev/null 111 | else 112 | git -C $local_wp_in_a_box_repo pull -q origin master &> /dev/null 113 | git -C $local_wp_in_a_box_repo pull -q --recurse-submodules &> /dev/null 114 | fi 115 | echo done. 116 | echo 117 | 118 | # pre-install steps 119 | case "$codename" in 120 | "focal") 121 | . $local_wp_in_a_box_repo/scripts/pre-install-focal.sh 122 | ;; 123 | "bionic") 124 | # . $local_wp_in_a_box_repo/scripts/pre-install-bionic.sh 125 | ;; 126 | "stretch") 127 | # . $local_wp_in_a_box_repo/scripts/pre-install-stretch.sh 128 | ;; 129 | "xenial") 130 | # . $local_wp_in_a_box_repo/scripts/pre-install-xenial.sh 131 | ;; 132 | "buster") 133 | ;; 134 | *) 135 | echo "Distro: $codename" 136 | echo 'Warning: Could not figure out the distribution codename. Skipping pre-install steps!' 137 | ;; 138 | esac 139 | 140 | . $local_wp_in_a_box_repo/scripts/swap.sh 141 | echo 142 | . $local_wp_in_a_box_repo/scripts/base-installation.sh 143 | echo 144 | . $local_wp_in_a_box_repo/scripts/linux-tweaks.sh 145 | echo 146 | . $local_wp_in_a_box_repo/scripts/nginx-installation.sh 147 | echo 148 | . $local_wp_in_a_box_repo/scripts/mysql-installation.sh 149 | echo 150 | . $local_wp_in_a_box_repo/scripts/wp-user-creation.sh 151 | echo 152 | . $local_wp_in_a_box_repo/scripts/php-installation.sh 153 | echo 154 | . $local_wp_in_a_box_repo/scripts/server-admin-creation.sh 155 | echo 156 | 157 | # the following can be executed at any order as they are mostly optional 158 | # . $local_wp_in_a_box_repo/scripts/firewall.sh 159 | echo 160 | 161 | # optional software, utilities and packages 162 | # . $local_wp_in_a_box_repo/scripts/optional-installation.sh 163 | 164 | # post-install steps 165 | case "$codename" in 166 | "focal") 167 | . $local_wp_in_a_box_repo/scripts/post-install-focal.sh 168 | ;; 169 | "bionic") 170 | . $local_wp_in_a_box_repo/scripts/post-install-bionic.sh 171 | ;; 172 | "stretch") 173 | . $local_wp_in_a_box_repo/scripts/post-install-stretch.sh 174 | ;; 175 | "xenial") 176 | . $local_wp_in_a_box_repo/scripts/post-install-xenial.sh 177 | ;; 178 | "buster") 179 | . $local_wp_in_a_box_repo/scripts/post-install-buster.sh 180 | ;; 181 | *) 182 | echo "Distro: $codename" 183 | echo 'Warning: Could not figure out the distribution codename. Skipping post-install steps!' 184 | ;; 185 | esac 186 | 187 | # configure some defaults for git and etckeeper 188 | git config --global user.name "root" 189 | git config --global user.email "root@localhost" 190 | git config --global init.defaultBranch main 191 | 192 | printf '%-72s' "Installing etckeeper..." 193 | if ! dpkg-query -W -f='${Status}' etckeeper 2> /dev/null | grep -q "ok installed"; then apt-get -qq install etckeeper &> /dev/null; fi 194 | sed -i 's/^GIT_COMMIT_OPTIONS=""$/GIT_COMMIT_OPTIONS="--quiet"/' /etc/etckeeper/etckeeper.conf 195 | echo done. 196 | 197 | # logout and then login to see the changes 198 | echo All done. 199 | 200 | echo ------------------------------------------------------------------------- 201 | echo You may find the login credentials of SFTP/SSH user in /root/.envrc file. 202 | echo ------------------------------------------------------------------------- 203 | 204 | echo 'You may reboot only once to apply certain updates (ex: kernel updates)!' 205 | echo 206 | 207 | echo "Script ended on (date & time): $(date +%c)" 208 | echo 209 | -------------------------------------------------------------------------------- /scripts/awscli-install-update-script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # set -x 7 | 8 | # to capture non-zero exit code in the pipeline 9 | set -o pipefail 10 | 11 | # what's done here 12 | # install aws cli depending on user (normal user or root) 13 | # if root, install aws cli in /usr/local/{aws-cli,bin} 14 | # if normal user, install it in ~/.local/{aws-cli,bin} 15 | 16 | # variables 17 | # none 18 | 19 | # ToDo: update via cron 20 | 21 | # check root user 22 | # https://stackoverflow.com/a/52586842/1004587 23 | # also see https://stackoverflow.com/q/3522341/1004587 24 | is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; } 25 | if is_user_root; then 26 | # echo 'You must be root or user with sudo privilege to run this script. Exiting now.'; exit 1; 27 | InstallDir=/usr/local/aws-cli 28 | BinDir=/usr/local/bin 29 | else 30 | InstallDir=~/.local/aws-cli 31 | BinDir=~/.local/bin 32 | # attempt to create InstallDir and BinDir 33 | [ -d $InstallDir ] || mkdir -p $InstallDir 34 | if [ "$?" -ne "0" ]; then 35 | echo "InstallDir is not found at $InstallDir. This script can't create it, either!" 36 | echo 'You may create it manually and re-run this script.' 37 | exit 1 38 | fi 39 | [ -d $BinDir ] || mkdir -p $BinDir 40 | if [ "$?" -ne "0" ]; then 41 | echo "BinDir is not found at $BinDir. This script can't create it, either!" 42 | echo 'You may create it manually and re-run this script.' 43 | exit 1 44 | fi 45 | fi 46 | 47 | export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin 48 | 49 | function install_awscli { 50 | #----- install AWS cli -----# 51 | printf '%-72s' "Installing awscli..." 52 | 53 | # for version #1 54 | # TODO: Update this function to install version #2 55 | # see - https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html 56 | 57 | # version #1 58 | # curl --silent "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "/tmp/awscli-bundle.zip" 59 | # unzip -qq -d /tmp/ /tmp/awscli-bundle.zip 60 | # sudo /tmp/awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws &> /dev/null 61 | 62 | # for version #2 63 | # ref: https://docs.aws.amazon.com/cli/latest/userguide/install-bundle.html 64 | curl --silent "https://awscli.amazonaws.com/awscli-exe-linux-$(arch).zip" -o "/tmp/awscliv2.zip" 65 | unzip -qq -d /tmp/ /tmp/awscliv2.zip 66 | /tmp/aws/install --install-dir $InstallDir --bin-dir $BinDir &> /dev/null # for installation 67 | if [ "$?" != "0" ]; then 68 | echo "Error installing aws cli!" 69 | fi 70 | 71 | # cleanup 72 | rm /tmp/awscliv2.zip 73 | rm -rf /tmp/aws 74 | 75 | # version #1 76 | # rm /tmp/awscli-bundle.zip 77 | # rm -rf /tmp/awscli-bundle 78 | echo done. 79 | } 80 | 81 | function update_awscli { 82 | #----- install AWS cli -----# 83 | printf '%-72s' "Updating awscli..." 84 | 85 | # remove the version #1 of aws cli, if exists 86 | [ -d /usr/local/aws ] && rm -rf /usr/local/aws &> /dev/null 87 | [ -f /usr/local/bin/aws ] && rm /usr/local/bin/aws &> /dev/null 88 | 89 | # for version #2 90 | # ref: https://docs.aws.amazon.com/cli/latest/userguide/install-bundle.html 91 | curl --silent "https://awscli.amazonaws.com/awscli-exe-linux-$(arch).zip" -o "/tmp/awscliv2.zip" 92 | unzip -qq -d /tmp/ /tmp/awscliv2.zip 93 | /tmp/aws/install --install-dir $InstallDir --bin-dir $BinDir --update 1> /dev/null 94 | if [ "$?" != "0" ]; then 95 | echo "Error installing aws cli!" 96 | fi 97 | 98 | # cleanup 99 | rm /tmp/awscliv2.zip 100 | rm -rf /tmp/aws 101 | 102 | echo done. 103 | } 104 | 105 | if [ $(which aws 2> /dev/null) ]; then 106 | update_awscli 107 | else 108 | install_awscli 109 | fi 110 | -------------------------------------------------------------------------------- /scripts/base-installation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | 8 | # what's done here 9 | 10 | # variables 11 | 12 | #--- Install pre-requisites ---# 13 | # landscape-common update-notifier-common \ 14 | echo Installing prerequisites... 15 | echo ----------------------------------------------------------------------------- 16 | required_packages="apt-transport-https \ 17 | bash-completion \ 18 | curl \ 19 | dnsutils \ 20 | language-pack-en \ 21 | unattended-upgrades apt-listchanges \ 22 | pwgen \ 23 | fail2ban \ 24 | software-properties-common \ 25 | sudo \ 26 | tzdata \ 27 | unzip \ 28 | wget" 29 | 30 | for package in $required_packages 31 | do 32 | # if dpkg-query -s $package &> /dev/null 33 | if dpkg-query -W -f='${Status}' $package 2>/dev/null | grep -q "ok installed" 34 | then 35 | echo "'$package' is already installed" 36 | else 37 | printf '%-72s' "Installing '${package}' ..." 38 | apt-get -qq install $package &> /dev/null 39 | echo done. 40 | fi 41 | done 42 | 43 | echo ------------------------------------------------------------------------- 44 | echo ... done installing prerequisites! 45 | echo 46 | 47 | if [ ! -s /var/spool/cron/crontabs/root ]; then 48 | echo 'Setting up crontab for root!' 49 | echo ' 50 | # ┌───────────── minute (0 - 59) 51 | # │ ┌───────────── hour (0 - 23) 52 | # │ │ ┌───────────── day of month (1 - 31) 53 | # │ │ │ ┌───────────── month (1 - 12) 54 | # │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday; 55 | # │ │ │ │ │ 7 is also Sunday on some systems) 56 | # │ │ │ │ │ 57 | # │ │ │ │ │ 58 | # * * * * * command to execute' | crontab - &>> $log_file 59 | fi 60 | 61 | web_dev=${DEV_USER:-""} 62 | if [ ! -z "$web_dev" ]; then 63 | # set up some defaults 64 | if [ ! -s /var/spool/cron/crontabs/$web_dev ]; then 65 | echo "Setting up crontab for $web_dev!" 66 | echo ' 67 | # ┌───────────── minute (0 - 59) 68 | # │ ┌───────────── hour (0 - 23) 69 | # │ │ ┌───────────── day of month (1 - 31) 70 | # │ │ │ ┌───────────── month (1 - 12) 71 | # │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday; 72 | # │ │ │ │ │ 7 is also Sunday on some systems) 73 | # │ │ │ │ │ 74 | # │ │ │ │ │ 75 | # * * * * * command to execute' | crontab -u $web_dev - &>> $log_file 76 | fi 77 | fi 78 | 79 | #--- setup timezone ---# 80 | current_time_zone=$(date +\%Z) 81 | if [ "$current_time_zone" != "UTC" ] ; then 82 | printf '%-72s' "Setting up timezone..." 83 | ln -fs /usr/share/zoneinfo/UTC /etc/localtime 84 | dpkg-reconfigure -f noninteractive tzdata &>> $log_file 85 | # timedatectl set-timezone UTC 86 | check_result $? 'Error setting up timezone.' 87 | systemctl restart cron 88 | check_result $? 'Error restarting cron daemon.' 89 | echo done. 90 | fi 91 | 92 | # printf '%-72s' "Setting up unattended upgrades..." 93 | #--- Unattended Upgrades ---# 94 | echo 'APT::Periodic::Update-Package-Lists "1";' >| /etc/apt/apt.conf.d/20auto-upgrades 95 | echo 'APT::Periodic::Unattended-Upgrade "1";' >> /etc/apt/apt.conf.d/20auto-upgrades 96 | 97 | # sed -i '/Unattended\-Upgrade::Mail/ s:^//::' /etc/apt/apt.conf.d/50unattended-upgrades 98 | # sed -i '/Unattended-Upgrade::MailOnlyOnError/ s:^//::' /etc/apt/apt.conf.d/50unattended-upgrades 99 | # if the following doesn't work, comment it and then uncomment the above two lines 100 | sed -i '/\/\/Unattended-Upgrade::Mail\(OnlyOnError\)\?/ s:^//::' /etc/apt/apt.conf.d/50unattended-upgrades 101 | # echo done. 102 | 103 | #--- setup permissions for .envrc file ---# 104 | if [ -f /root/.envrc ]; then 105 | chmod 600 /root/.envrc 106 | . /root/.envrc 107 | # direnv allow &> /dev/null 108 | fi 109 | 110 | #--- Setup wp cli ---# 111 | if [ ! -s /usr/local/bin/wp ]; then 112 | printf '%-72s' "Setting up WP CLI..." 113 | wp_cli_url=https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 114 | curl -LSsO $wp_cli_url 115 | check_result $? 'wp-cli: error downloading the script.' 116 | chmod +x wp-cli.phar 117 | mv wp-cli.phar /usr/local/bin/wp 118 | 119 | echo done. 120 | fi 121 | 122 | # wp cli bash completion 123 | if [ ! -s /etc/bash_completion.d/wp-completion.bash ]; then 124 | curl -LSso /etc/bash_completion.d/wp-completion.bash https://github.com/wp-cli/wp-cli/raw/master/utils/wp-completion.bash 125 | fi 126 | 127 | #--- cron: auto-update wp-cli ---# 128 | grep -qw wp-cli /var/spool/cron/crontabs/root 129 | if [ "$?" -ne "0" ]; then 130 | ( crontab -l; echo; echo "# auto-update wp-cli" ) | crontab - 131 | ( crontab -l; echo '@daily /usr/local/bin/wp cli update --allow-root --yes &> /dev/null' ) | crontab - 132 | fi 133 | 134 | #--- auto-renew SSL certs ---# 135 | # check for the line with the text "certbot" 136 | crontab -l | grep -qw certbot 137 | if [ $? -ne 0 ]; then 138 | ( crontab -l; echo; echo "# auto-renew SSL certs" ) | crontab - 139 | ( crontab -l; echo '@daily /usr/bin/certbot renew --post-hook "/usr/sbin/nginx -t && /usr/sbin/service nginx reload" &> /dev/null' ) | crontab - 140 | fi 141 | 142 | 143 | #--- cron tweaks ---# 144 | #--- separate cron log ---# 145 | # if ! grep -q '# Log cron stuff' /etc/rsyslog.conf ; then 146 | # echo '# Log cron stuff' > /etc/rsyslog.conf 147 | # echo "cron.* /var/log/cron" >> /etc/rsyslog.conf 148 | # fi 149 | sed -i -e 's/^#cron.*/cron.*/' /etc/rsyslog.conf 150 | 151 | #- log only errors -# 152 | # the following solution may not work in the future, as /etc/default/cron is being deprecated! 153 | sed -i -e 's/^#EXTRA_OPTS=""$/EXTRA_OPTS=""/' -e 's/^EXTRA_OPTS=""$/EXTRA_OPTS="-L 0"/' /etc/default/cron 154 | systemctl restart syslog 155 | systemctl restart cron 156 | 157 | -------------------------------------------------------------------------------- /scripts/bootstrap-root.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Download common-aliases-envvars and insert into ~/.bashrc 4 | # Configure root-specific ~/.bashrc changes such as terminal color (red for root). 5 | # Configure crontab, such as certbot renewal. 6 | # Configure vim 7 | 8 | [ -d ~/.config ] || mkdir ~/.config 9 | 10 | [ -s ~/.config/common-aliases-envvars ] || wget -q -O ~/.config/common-aliases-envvars https://raw.githubusercontent.com/pothi/snippets/main/linux/common-aliases-envvars 11 | . ~/.config/common-aliases-envvars 12 | 13 | if ! grep -qw common-aliases.envvars ~/.bashrc ; then 14 | echo -e "\n[ -f ~/.config/common-aliases-envvars ] && source ~/.config/common-aliases-envvars\n" >> ~/.bashrc 15 | fi 16 | 17 | # Load ~/.envrc file if exists 18 | if ! grep -qF envrc ~/.bashrc ; then 19 | echo -e "\n[ -f ~/.envrc ] && . ~/.envrc\n" >> ~/.bashrc 20 | fi 21 | 22 | ###------------------------------ setup color for root terminal ------------------------------### 23 | rootbashrc=/root/.bashrc 24 | entry='#red_color for root' 25 | if [ -f $rootbashrc ]; then 26 | if ! grep -q "^${entry}$" "$rootbashrc" ; then 27 | echo -e "\n${entry}\n" >> $rootbashrc 28 | echo 'PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;31m\]\u\[\033[01;33m\]@\[\033[01;36m\]\h \[\033[01;33m\]\w \[\033[01;35m\]\$ \[\033[00m\]"' >> $rootbashrc 29 | printf '\n' >> $rootbashrc 30 | . $rootbashrc 31 | fi # test if the entry is found in file 32 | fi # test if file exists 33 | 34 | ###------------------------------ VIM Tweaks ------------------------------### 35 | [ -d ~/.vim ] || mkdir ~/.vim 36 | [ -d ~/git/snippets ] || { 37 | git clone -q --depth 1 https://github.com/pothi/snippets ~/git/snippets 38 | cp -a ~/git/snippets/vim/* ~/.vim/ 39 | } 40 | 41 | ###------------------------------ ps_mem.py ------------------------------### 42 | apt-get -qq install python-is-python3 > /dev/null 43 | [ -d ~/.local/bin ] || mkdir -p ~/.local/bin 44 | ps_mem_file=~/.local/bin/ps_mem.py 45 | if [ ! -f "$ps_mem_file" ]; then 46 | printf '%-72s' "Downloading ps_mem.py script..." 47 | script_url=http://www.pixelbeat.org/scripts/ps_mem.py 48 | wget -q -O ~/.local/bin/ps_mem.py $script_url 49 | # check_result $? 'ps_mem.py: error downloading the script.' 50 | chmod +x "$ps_mem_file" 51 | echo done. 52 | fi 53 | 54 | # TODO: Download tuning-primer, mysqlturner scripts 55 | # have a cron to periodically run the above scripts with logs saved every day. 56 | # tuning-primer is available in two places 57 | # 1. https://launchpad.net/mysql-tuning-primer/ 58 | # 2. https://github.com/BMDan/tuning-primer.sh 59 | -------------------------------------------------------------------------------- /scripts/bootstrap-server-admin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Download common-aliases-envvars and insert into ~/.bashrc 4 | # Configure root-specific ~/.bashrc changes such as terminal color (orange or blue for server admin). 5 | # Configure vim 6 | 7 | [ ! -d ~/.config ] && mkdir ~/.config 8 | 9 | [ ! -s ~/.config/common-aliases-envvars ] && wget -q -O ~/.config/common-aliases-envvars https://raw.githubusercontent.com/pothi/snippets/main/linux/common-aliases-envvars 10 | . ~/.config/common-aliases-envvars 11 | 12 | if ! grep -qw common-aliases.envvars ~/.bashrc ; then 13 | printf "[[ -f ~/.config/common-aliases-envvars ]] && source ~/.config/common-aliases-envvars\n" >> ~/.bashrc 14 | fi 15 | 16 | if ! grep -qF custom-aliases-envvars-custom ~/.bashrc ; then 17 | echo "[[ -f ~/.config/custom-aliases-envvars-custom ]] && . ~/.config/custom-aliases-envvars-custom" >> ~/.bashrc 18 | fi 19 | 20 | # Load ~/.envrc file if exists 21 | if ! grep -qF envrc ~/.bashrc ; then 22 | echo "[ -f ~/.envrc ] && . ~/.envrc" >> ~/.bashrc 23 | fi 24 | 25 | ###------------------------------ setup color for server-admin terminal ------------------------------### 26 | adminbashrc="~/.bashrc" 27 | entry='#color for server admin' 28 | # if [ -f $adminbashrc ]; then 29 | # if ! $(grep -q "^${entry}$" "$adminbashrc") ; then 30 | # printf "\n${entry}\n" >> $adminbashrc 31 | # PS1 taken from Ubuntu Jammy (22.04) 32 | # echo 'PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\#"' >> $adminbashrc 33 | # echo 'PS1="\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;34m\]\u\[\033[01;33m\]@\[\033[01;36m\]\h \[\033[01;33m\]\w \[\033[01;35m\]\$ \[\033[00m\]"' >> $adminbashrc 34 | # printf '\n' >> $adminbashrc 35 | # . $adminbashrc 36 | # fi # test if the entry is found in file 37 | # fi # test if file exists 38 | 39 | ###------------------------------ VIM Tweaks ------------------------------### 40 | 41 | [ ! -d ~/.vim ] && mkdir ~/.vim 42 | [ ! -d ~/git/snippets ] && { 43 | git clone -q --depth 1 https://github.com/pothi/snippets ~/git/snippets 44 | cp -a ~/git/snippets/vim/* ~/.vim/ 45 | } 46 | 47 | -------------------------------------------------------------------------------- /scripts/bootstrap-wp-user.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Download common-aliases-envvars and insert into ~/.bashrc 4 | # Download backup scripts. 5 | # Configure vim 6 | 7 | # optional 8 | # - Install node. 9 | # - Download wp-cli and install it locally. 10 | # - Install AWS CLI if needed. 11 | # - Install GCloud utils if needed. 12 | 13 | # To debug, use any value for "debug", otherwise please leave it empty 14 | debug= 15 | 16 | [ ! -d ~/scripts ] && mkdir ~/scripts 17 | 18 | # helper function to exit upon non-zero exit code of a command 19 | # usage some_command; check_result $? 'some_command failed' 20 | if ! $(type 'check_result' 2>/dev/null | grep -q 'function') ; then 21 | check_result() { 22 | if [ "$1" -ne 0 ]; then 23 | echo -e "\nError: $2. Exiting!\n" 24 | exit "$1" 25 | fi 26 | } 27 | fi 28 | 29 | #-------------------- Unused --------------------# 30 | configure_disk_usage_alert() { 31 | [ ! -f ~/scripts/disk-usage-alert.sh ] && wget -O ~/scripts/disk-usage-alert.sh https://github.com/pothi/snippets/raw/master/disk-usage-alert.sh 32 | chown $wp_user:$wp_user ~/scripts/disk-usage-alert.sh 33 | chmod +x ~/scripts/disk-usage-alert.sh 34 | 35 | #--- cron for disk-usage-alert ---# 36 | crontab -l | grep -qw disk-usage-alert 37 | if [ "$?" -ne "0" ]; then 38 | ( crontab -l; echo '@daily ~/scripts/disk-usage-alert.sh &> /dev/null' ) | crontab - 39 | fi 40 | } 41 | # configure_disk_usage_alert 42 | 43 | [ "$debug" ] && set -x 44 | 45 | #-------------------- Git config --------------------# 46 | 47 | git config --global init.defaultBranch main 48 | 49 | #-------------------- Download backup scripts --------------------# 50 | # Download backup scripts 51 | echo 'Downloading backup scripts...' 52 | FULL_BACKUP_URL=https://github.com/pothi/backup-wordpress/raw/main/full-backup.sh 53 | DB_BACKUP_URL=https://github.com/pothi/backup-wordpress/raw/main/db-backup.sh 54 | FILES_BACKUP_URL=https://github.com/pothi/backup-wordpress/raw/main/files-backup-without-uploads.sh 55 | # cd ~/scripts 56 | # [ ! -s full-backup.sh ] && curl -LSsO $FULL_BACKUP_URL 57 | # [ ! -s db-backup.sh ] && curl -LSsO $DB_BACKUP_URL 58 | # [ ! -s files-backup-without-uploads.sh ] && curl -LSsO $FILES_BACKUP_URL 59 | [ ! -s ~/scripts/full-backup.sh ] && curl -s --output-dir ~/scripts -O $FULL_BACKUP_URL 60 | [ ! -s ~/scripts/db-backup.sh ] && curl -s --output-dir ~/scripts -O $DB_BACKUP_URL 61 | [ ! -s ~/scripts/files-backup-without-uploads.sh ] && curl -s --output-dir ~/scripts -O $FILES_BACKUP_URL 62 | chmod +x ~/scripts/*.sh 63 | # cd - >/dev/null 64 | # echo '... done' 65 | 66 | #-------------------- Configure common-aliases-envvars --------------------# 67 | echo 'Tweaking bash config...' 68 | [ ! -d ~/.config ] && mkdir ~/.config 69 | 70 | echo >> ~/.bashrc 71 | 72 | [ ! -s ~/.config/common-aliases-envvars ] && curl -s --output-dir ~/.config -O https://github.com/pothi/snippets/raw/main/linux/common-aliases-envvars 73 | . ~/.config/common-aliases-envvars 74 | 75 | if ! grep -q common-aliases-envvars ~/.bashrc ; then 76 | echo "[ -f ~/.config/common-aliases-envvars ] && source ~/.config/common-aliases-envvars" >> ~/.bashrc 77 | fi 78 | 79 | if ! grep -qF custom-aliases-envvars-custom ~/.bashrc ; then 80 | echo "[ -f ~/.config/custom-aliases-envvars-custom ] && . ~/.config/custom-aliases-envvars-custom" >> ~/.bashrc 81 | fi 82 | 83 | # Load ~/.envrc file if exists 84 | if ! grep -qF envrc ~/.bashrc ; then 85 | echo "[ -f ~/.envrc ] && . ~/.envrc" >> ~/.bashrc 86 | fi 87 | 88 | echo >> ~/.bashrc 89 | 90 | #-------------------- Configure VIM --------------------# 91 | echo 'Tweaking VIM config...' 92 | [ ! -d ~/.vim ] && mkdir ~/.vim 93 | [ ! -d ~/git/snippets ] && { 94 | git clone -q --depth 1 https://github.com/pothi/snippets ~/git/snippets 95 | cp -a ~/git/snippets/vim/* ~/.vim/ 96 | # run the following only when not debugging! 97 | if [ ! "$debug" ]; then 98 | rm -rf ~/git/snippets 99 | rmdir ~/git &> /dev/null 100 | fi 101 | } 102 | 103 | #-------------------- Install wp-cli --------------------# 104 | # echo 'Installing wp-cli...' 105 | if ! command -v wp >/dev/null; then 106 | curl -s --output-dir ~/ -O https://github.com/pothi/wp-in-a-box/raw/main/scripts/wp-cli-install.bash 107 | bash ~/wp-cli-install.bash && rm ~/wp-cli-install.bash 108 | check_result $? "Could not install wp-cli." 109 | fi 110 | 111 | #-------------------- Install aws-cli --------------------# 112 | # echo 'Installing aws-cli...' 113 | if ! command -v aws >/dev/null; then 114 | curl -s --output-dir ~/ -O https://github.com/pothi/wp-in-a-box/raw/main/scripts/awscli-install-update-script.sh 115 | bash ~/awscli-install-update-script.sh && rm ~/awscli-install-update-script.sh 116 | check_result $? "Could not install aws-cli." 117 | fi 118 | 119 | #-------------------- Create SSH keys --------------------# 120 | echo 'Creating local SSH keys...' 121 | < /dev/zero ssh-keygen -q -N "" -t ed25519 122 | echo 123 | 124 | #-------------------- Bootstrap timers to alert upon auto-reboot --------------------# 125 | # TODO: Might not work if logged-in through root 126 | echo 'Configuring alerts upon auto-reboot...' 127 | if ! command -v aws >/dev/null; then 128 | curl -s --output-dir ~/ -O https://github.com/pothi/snippets/raw/main/linux/alert-auto-reboot/bootstrap.sh 129 | bash ~/bootstrap.sh && rm ~/bootstrap.sh 130 | check_result $? "Could not bootstrap timers to alert upon auto-reboot." 131 | fi 132 | 133 | 134 | -------------------------------------------------------------------------------- /scripts/cron-tweaks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # export DEBIAN_FRONTEND=noninteractive 7 | 8 | # what's done here 9 | 10 | # variables 11 | 12 | #--- cron tweaks ---# 13 | #--- separate cron log ---# 14 | # if ! grep -q '# Log cron stuff' /etc/rsyslog.conf ; then 15 | # echo '# Log cron stuff' > /etc/rsyslog.conf 16 | # echo "cron.* /var/log/cron" >> /etc/rsyslog.conf 17 | # fi 18 | # sed -i -e 's/^#cron.*/cron.*/' /etc/rsyslog.d/50-default.conf 19 | echo 'cron.* /var/log/cron.log' > /etc/rsyslog.d/90-cron.conf 20 | 21 | #- log only errors -# 22 | # the following solution may not work in the future, as /etc/default/cron is being deprecated! 23 | # sed -i -e 's/^#EXTRA_OPTS=""$/EXTRA_OPTS=""/' -e 's/^EXTRA_OPTS=""$/EXTRA_OPTS="-L 0"/' /etc/default/cron 24 | 25 | systemctl restart syslog 26 | systemctl restart cron 27 | 28 | 29 | -------------------------------------------------------------------------------- /scripts/deny-root-login.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash sh 2 | 3 | #TODO: Check if the version of OpenSSH is 8.2 or greater 4 | cd /etc/ssh/sshd_config.d 5 | if [ ! -f deny-root-login.conf ]; then 6 | echo "PermitRootLogin no" > deny-root-login.conf 7 | fi 8 | cd - 1> /dev/null 9 | 10 | /usr/sbin/sshd -t && systemctl restart sshd 11 | -------------------------------------------------------------------------------- /scripts/email-mta-installation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | 8 | check_result() { 9 | if [ $1 -ne 0 ]; then 10 | echo "Error: $2" 11 | exit $1 12 | fi 13 | } 14 | # TODO 15 | # get FQDN and configure myhostname in postfix 16 | 17 | # variables 18 | 19 | # optional parameters 20 | # EMAIL 21 | # SMTP_USERNAME= 22 | # SMTP_PASSWORD= 23 | # SMTP_HOST= 24 | # SMTP_PORT= 25 | 26 | mta=postfix 27 | 28 | echo 'Installing / setting up MTA...' 29 | 30 | # dependencies 31 | # https://serverfault.com/a/325975/102173 32 | # rsyslog is missing in Ubuntu minimal image 33 | # To have mail.log, rsyslog is required. 34 | apt-get install -qq $mta rsyslog libsasl2-modules mailutils pflogsumm &> /dev/null 35 | 36 | # take a backup before making changes 37 | [ -d ~/backups ] || mkdir ~/backups 38 | [ -f "$HOME/backups/postfix-default-$(date +%F)" ] || cp -a /etc/postfix ~/backups/postfix-default-"$(date +%F)" 39 | 40 | # setup mta to use only ipv4 to send emails 41 | #- why: 42 | #- https://support.google.com/mail/?p=IPv6AuthError 43 | #- every host doesn't support IPv6 44 | #- every host doesn't support setting up reverse DNS 45 | #- Linode: when swapping IPs, it only swaps IPv4, not IPv6 :( 46 | postconf -e 'inet_protocols = ipv4' 47 | 48 | # encrypt outgoing emails 49 | # ref: http://blog.snapdragon.cc/2013/07/07/setting-postfix-to-encrypt-all-traffic-when-talking-to-other-mailservers/ 50 | postconf -e 'smtp_tls_security_level = encrypt' 51 | # postconf -e 'smtp_tls_wrappermode = yes' 52 | # postconf -e 'smtpd_tls_security_level = may' 53 | # postconf -e 'smtp_tls_loglevel = 1' 54 | # postconf -e 'smtpd_tls_loglevel = 1' 55 | # ref: https://serverfault.com/q/858311/102173 56 | # postconf -e 'smtp_tls_CApath = /etc/ssl/certs' 57 | # postconf -e 'smtpd_tls_CApath = /etc/ssl/certs' 58 | 59 | # only applicable to Debian and Ubuntu 60 | # postconf -e 'smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt' 61 | 62 | # postconf -e 'smtp_use_tls = yes' 63 | # postconf -e 'smtp_tls_note_starttls_offer = yes' 64 | 65 | # limit outgoing rate 66 | postconf -e 'smtp_destination_concurrency_limit = 2' 67 | postconf -e 'smtp_destination_rate_delay = 60s' 68 | 69 | # listen only to localhost (to avoid exposing SMTP port 25 to outside world) 70 | postconf -e 'inet_interfaces = 127.0.0.1' 71 | 72 | # look for spam 73 | # postconf -e 'header_checks = regexp:/etc/postfix/header_checks' 74 | # postconf -e 'smtp_header_checks = regexp:/etc/postfix/header_checks' 75 | 76 | [ -f "$HOME/backups/postfix-$(date +%F)" ] || cp -a /etc/postfix ~/backups/postfix-"$(date +%F)" 77 | 78 | /usr/sbin/postfix check && systemctl restart $mta 79 | 80 | check_result $? "Warning: Something went wrong while restarting MTA ($mta). Continuing..." 81 | 82 | echo "... done setting up MTA (${mta})!" 83 | -------------------------------------------------------------------------------- /scripts/firewall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | 8 | echo 'Setting up ufw...' 9 | 10 | package="ufw" 11 | if dpkg-query -s $package &> /dev/null 12 | then 13 | echo "$package is already installed" 14 | else 15 | printf '%-72s' "Installing ${package}..." 16 | apt-get -qq install $package &> /dev/null 17 | echo done. 18 | fi 19 | 20 | # UFW 21 | ufw default deny incoming 22 | 23 | ufw allow 22 24 | ufw allow 80 25 | ufw allow 443 26 | ufw limit ssh comment 'Rate limit for SSH server' 27 | ufw allow from 192.168.0.0/16 28 | ufw allow from 172.16.0.0/12 29 | ufw allow from 10.0.0.0/8 30 | 31 | ufw --force enable 32 | if [ $? != 0 ]; then 33 | echo 'Error setting up firewall' 34 | fi 35 | 36 | echo "... done setting up UFW!" 37 | -------------------------------------------------------------------------------- /scripts/hostname.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # variables 9 | 10 | # Sets up hostname and FQDN, if they are not already set, such as in Linode 11 | # DigitalOcean sets it up correctly 12 | 13 | # To be run as root 14 | 15 | # FQDN = MY_HOSTNAME.MY_DOMAIN 16 | #--- Variables 17 | export MY_HOSTNAME= 18 | export MY_DOMAIN= 19 | # check if the above are empty 20 | # check if the above are already set in some file, for example in zshrc or bashrc 21 | # check if hostname is valid (single word) 22 | # check if the domainname is valid (at least two words) 23 | 24 | # TODO: Check if FQDN has been set already 25 | # For example, check for the string 'local' 26 | # check for the number of 'dot's (at least three needed) 27 | # check if hostname contains a single world 28 | # check if the hostname is present in /etc/hosts file 29 | # check if the hostname is present in the FQDN 30 | # check if the domainname contains at least two words 31 | # check if the domainname is part of the FQDN 32 | # get the hostname from /etc/hostname 33 | 34 | sed -i "1i127.0.11.1 ${MY_HOSTNAME}.${MY_DOMAIN} $MY_HOSTNAME" /etc/hosts 35 | echo $MY_HOSTNAME > /etc/hostname 36 | hostname -F /etc/hostname 37 | 38 | -------------------------------------------------------------------------------- /scripts/linux-tweaks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | local_wp_in_a_box_repo=/root/git/wp-in-a-box 4 | . ${HOME}/.envrc 5 | 6 | #--- Common for all users ---# 7 | echo Setting up linux tweaks... 8 | # echo ----------------------------------------------------------------------------- 9 | 10 | mkdir -p /etc/skel/{.aws,.cache,.composer,.config,.gnupg,.gsutil,.local/bin,.npm,.ssh,.vim,.wp-cli} &> /dev/null 11 | mkdir -p /etc/skel/{git,log,scripts,sites,tmp} &> /dev/null 12 | 13 | touch /etc/skel/{.bash_history,.gitconfig,.npmrc,.yarnrc,mbox,.selected-editor,.viminfo} 14 | 15 | chmod 600 /etc/skel/mbox 16 | chmod 700 /etc/skel/{.gnupg,.ssh} 17 | 18 | cp $local_wp_in_a_box_repo/snippets/linux/common-aliases-envvars /etc/skel/.config/ 19 | 20 | if ! grep -qw common-aliases-envvars /etc/skel/.bashrc ; then 21 | printf " [ -f ~/.config/common-aliases-envvars ] && . ~/.config/common-aliases-envvars " >> /etc/skel/.bashrc 22 | fi 23 | 24 | # end of ~/.bashrc tweaks 25 | 26 | ###--- VIM Tweaks ---### 27 | # copy only vimrc to normal users 28 | [ ! -d /etc/skel/.vim ] && mkdir /etc/skel/.vim 29 | cp $local_wp_in_a_box_repo/snippets/vim/vimrc ~/.vim/ 30 | 31 | #--- Tweak SSH config ---# 32 | 33 | # disable password authentication 34 | sshd_config_file=/etc/ssh/sshd_config 35 | sed -i -E '/PasswordAuthentication (yes|no)/ s/^#//' $sshd_config_file 36 | # replace only the first occurrance of the text PasswordAuthentication 37 | sed -i '0,/PasswordAuthentication/I s/yes/no/' $sshd_config_file 38 | sed -i '0,/PubkeyAuthentication/I s/no/yes/' $sshd_config_file 39 | 40 | # echo 'Testing the modified SSH config' 41 | # the following didn't work 42 | # sshd –t 43 | # /usr/sbin/sshd -t 44 | # if [ "$?" != 0 ]; then 45 | # echo 'Something is messed up in the SSH config file' 46 | # echo 'Please re-run after fixing errors' 47 | # echo "See the logfile ${log_file} for details of the error" 48 | # echo 'Exiting pre-maturely' 49 | # exit 1 50 | # else 51 | # echo 'Cool. Things seem fine.' 52 | # echo "Restarting SSH daemon..." 53 | # printf '%-72s' "Restarting SSH daemon..." 54 | systemctl restart sshd &> /dev/null 55 | if [ $? -ne 0 ]; then 56 | echo 'Something went wrong while restarting SSH! See below...'; echo; echo; 57 | systemctl status sshd 58 | # else 59 | # echo '... SSH daemon restarted!' 60 | # echo done. 61 | fi 62 | # fi 63 | 64 | 65 | #--- Tweak Logwatch ---# 66 | if [ -f /etc/cron.daily/00logwatch ]; then 67 | mv /etc/cron.daily/00logwatch /etc/cron.weekly/00logwatch &> /dev/null 68 | check_result $? 'Error moving logwatch cron file' 69 | 70 | logwatch_conf_file=/etc/logwatch/conf/logwatch.conf 71 | touch $logwatch_conf_file 72 | echo 'Range = "between -7 days and -1 days"' >> $logwatch_conf_file 73 | echo 'Details = High' >> $logwatch_conf_file 74 | if [ ! -z "$EMAIL" ]; then 75 | echo "mailto = $EMAIL" >> $logwatch_conf_file 76 | fi 77 | 78 | if [ ! -z "$WP_DOMAIN" ]; then 79 | echo "MailFrom = logwatch@$WP_DOMAIN" >> $logwatch_conf_file 80 | echo "Subject = 'Weekly log from $WP_DOMAIN server'" >> $logwatch_conf_file 81 | fi 82 | 83 | fi # test for logwatch cron 84 | 85 | #--- Put /etc/ under version control ---# 86 | # entry='vim/plugged' 87 | # if [ -f /etc/.gitignore ] ; then 88 | # if ! $(grep -q "^${entry}$" "/etc/.gitignore") ; then 89 | # printf " 90 | # ref: http://fallengamer.livejournal.com/93321.html 91 | # https://stackoverflow.com/q/1274057/1004587 92 | # basically two methods 93 | # https://stackoverflow.com/a/34511442/1004587 94 | # https://stackoverflow.com/a/44098435/1004587 95 | 96 | # vim/plugged 97 | # " >> /etc/.gitignore 98 | # fi # test if entry is found in file 99 | # fi # test if file exists 100 | 101 | #--- Misc Tweaks ---# 102 | sed -i 's/^#\(startup_message off\)$/\1/' /etc/screenrc 103 | 104 | # ref: https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers#the-technical-details 105 | # to fix "Error: ENOSPC: System limit for number of file watchers reached" - when running "watch" with gulp, grunt, etc. 106 | file_watchers_limit_sysctl_file='/etc/sysctl.d/60-file-watchers-limit-local.conf' 107 | # create / overwrite and append our custom values in it 108 | printf "fs.inotify.max_user_watches = 524288\n" > $file_watchers_limit_sysctl_file &> /dev/null 109 | sysctl -p $file_watchers_limit_sysctl_file 110 | 111 | # echo ------------------------------------------------------------------------- 112 | echo ... linux tweaks are done. 113 | -------------------------------------------------------------------------------- /scripts/lowendtalk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin 8 | 9 | # what's done here 10 | # fine-tune low end servers. 11 | 12 | # variables 13 | JOURNALD_MAX_MEM=512M 14 | 15 | # Journald tweak - limit disk usage. 16 | # ref: https://blog.tuxclouds.org/posts/journalctl-clean-up-and-tricks/ 17 | # to reduce the usage 18 | # journalctl --vacuum-size=512M 19 | # verify current disk usage 20 | # journalctl --disk-usage 21 | [ -d /etc/systemd/journald.conf.d ] || mkdir /etc/systemd/journald.conf.d 22 | if [ -f /etc/systemd/journald.conf.d/custom.conf ]; then 23 | echo -e "[Journal]\nSystemMaxUse=$JOURNALD_MAX_MEM" > /etc/systemd/journald.conf.d/custom.conf 24 | systemctl restart systemd-journald 25 | fi 26 | 27 | # Disable binlog 28 | systemctl stop mysql 29 | echo 'skip-log-bin = true' > /etc/mysql/mysql.conf.d/80-skip-log-bin.conf 30 | systemctl start mysql 31 | # Remove existing log files 32 | rm /var/lib/mysql/binlog.* 33 | -------------------------------------------------------------------------------- /scripts/memcached-install.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | 8 | # what's done here. 9 | # install php{ver}-memcached and php{ver}-memcache packages 10 | # update PHP session handling to memcache 11 | # configure memory for memcached server 12 | 13 | # Post install steps 14 | # - install https://wordpress.org/plugins/memcached/#installation 15 | # - (or) download https://plugins.svn.wordpress.org/memcached/trunk/object-cache.php into wp-content dir. 16 | # - configure WP_CACHE_KEY_SALT using the command... wp config set WP_CACHE_KEY_SALT $(openssl rand -base64 32) 17 | 18 | # variables 19 | 20 | echo "Setting up memcached..." 21 | 22 | if [ "$MEMCACHED_MEM_LIMIT" == "" ]; then 23 | MEMCACHED_MEM_LIMIT=64 24 | fi 25 | 26 | PHP_VER=8.3 27 | PHP_INI=/etc/php/${PHP_VER}/fpm/php.ini 28 | 29 | # Memcached server 30 | if ! apt-cache show memcached &> /dev/null ; then echo 'Memcached server not found!' ; fi 31 | apt-get install memcached -y 32 | cp /etc/memcached.conf /root/backups/memcached.conf-$(date +%F) 33 | sed -i '/^.m / s/[0-9]\+/'$MEMCACHED_MEM_LIMIT'/' /etc/memcached.conf 34 | systemctl restart memcached 35 | 36 | PHP_PACKAGES='' 37 | # at times the version number is not included for memcache(d) extension; let's check it 38 | # if apt-cache show php-memcached &> /dev/null ; then 39 | # PHP_PACKAGES=$(echo "$PHP_PACKAGES" 'php-memcached') 40 | # else 41 | PHP_PACKAGES=$(echo "$PHP_PACKAGES" "php${PHP_VER}-memcached") 42 | # fi 43 | 44 | # if apt-cache show php-memcache &> /dev/null ; then 45 | # PHP_PACKAGES=$(echo "$PHP_PACKAGES" 'php-memcache') 46 | # else 47 | PHP_PACKAGES=$(echo "$PHP_PACKAGES" "php${PHP_VER}-memcache") 48 | # fi 49 | 50 | apt-get -qq install ${PHP_PACKAGES} 51 | 52 | # SESSION Handling 53 | sed -i -e '/^session.save_handler/ s/=.*/= memcached/' $PHP_INI 54 | sed -i -e '/^;session.save_path/ s/.*/session.save_path = "127.0.0.1:11211"/' $PHP_INI 55 | 56 | systemctl restart php${PHP_VER}-fpm 57 | if [ "$?" != 0 ]; then 58 | echo 'PHP-FPM failed to restart after integrating memcached. Please check your configs!' 59 | fi 60 | 61 | echo "... done setting up memcached!" 62 | -------------------------------------------------------------------------------- /scripts/mysql-installation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | 8 | # echo 'Installing MySQL / MariaDB Server' 9 | echo 'Installing MySQL Server' 10 | # lets check if mariadb-server exists 11 | # sql_server=mariadb-server 12 | # if ! apt-cache show mariadb-server &> /dev/null ; then sql_server=mysql-server ; fi 13 | 14 | sql_server=default-mysql-server 15 | 16 | apt-get install pwgen ${sql_server} -qq &> /dev/null 17 | 18 | # systemctl stop mysql 19 | # enable slow log and other tweaks 20 | # local_wp_in_a_box_repo=/root/git/wp-in-a-box 21 | # cp $local_wp_in_a_box_repo/config/mysql.conf.d/*.cnf /etc/mysql/conf.d/ 22 | # systemctl start mysql 23 | 24 | [ -f /root/.envrc ] && . /root/.envrc 25 | 26 | echo 'Setting up MySQL admin user...' 27 | 28 | if [ "$ADMIN_USER" == "" ]; then 29 | # create MYSQL username automatically 30 | ADMIN_USER="sql_$(pwgen -Av 6 1)" 31 | ADMIN_PASS=$(pwgen -cnsv 20 1) 32 | echo "export ADMIN_USER=$ADMIN_USER" >> /root/.envrc 33 | echo "export ADMIN_PASS=$ADMIN_PASS" >> /root/.envrc 34 | mysql -e "CREATE USER ${ADMIN_USER} IDENTIFIED BY '${ADMIN_PASS}';" 35 | mysql -e "GRANT ALL PRIVILEGES ON *.* TO ${ADMIN_USER} WITH GRANT OPTION" 36 | fi 37 | echo ... done setting up MySQL user. 38 | 39 | echo ... done installing MySQL / MariaDB server! 40 | -------------------------------------------------------------------------------- /scripts/nginx-installation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | 8 | echo 'Installing Nginx Server...' 9 | 10 | # install prerequisites 11 | # ref: https://nginx.org/en/linux_packages.html#Ubuntu 12 | sudo apt-get -qq install curl gnupg2 ca-certificates lsb-release > /dev/null 13 | 14 | if ! $(type 'codename' 2>/dev/null | grep -q 'function') 15 | then 16 | codename() { 17 | lsb_release_cli=$(which lsb_release) 18 | local codename="" 19 | if [ ! -z $lsb_release_cli ]; then 20 | codename=$($lsb_release_cli -cs) 21 | else 22 | codename=$(cat /etc/os-release | awk -F = '/VERSION_CODENAME/{print $2}') 23 | fi 24 | echo "$codename" 25 | } 26 | codename=$(codename) 27 | fi 28 | 29 | # function to add the official Nginx.org repo 30 | nginx_repo_add() { 31 | distro=$(awk -F= '/^ID=/{print $2}' /etc/os-release) 32 | 33 | if ! $(type 'codename' 2>/dev/null | grep -q 'function') 34 | then 35 | codename() { 36 | lsb_release_cli=$(which lsb_release) 37 | local codename="" 38 | if [ ! -z $lsb_release_cli ]; then 39 | codename=$($lsb_release_cli -cs) 40 | else 41 | codename=$(cat /etc/os-release | awk -F = '/VERSION_CODENAME/{print $2}') 42 | fi 43 | echo "$codename" 44 | } 45 | codename=$(codename) 46 | fi 47 | 48 | [ -f nginx_signing.key ] && rm nginx_signing.key 49 | curl -LSsO http://nginx.org/keys/nginx_signing.key 50 | check_result $? 'Nginx key could not be downloaded!' 51 | apt-key add nginx_signing.key &> /dev/null 52 | check_result $? 'Nginx key could not be added!' 53 | rm nginx_signing.key 54 | 55 | # for updated info, please see https://nginx.org/en/linux_packages.html#stable 56 | nginx_branch= # leave this empty to install stable version 57 | # or nginx_branch="mainline" 58 | 59 | if [ "$nginx_branch" == 'mainline' ]; then 60 | nginx_src_url="https://nginx.org/packages/mainline/${distro}/" 61 | else 62 | nginx_src_url="https://nginx.org/packages/${distro}/" 63 | fi 64 | 65 | echo "deb [arch=amd64] ${nginx_src_url} ${codename} nginx" > /etc/apt/sources.list.d/nginx.list 66 | echo "deb-src [arch=amd64] ${nginx_src_url} ${codename} nginx" >> /etc/apt/sources.list.d/nginx.list 67 | 68 | # finally update the local apt cache 69 | apt-get update -qq > /dev/null 70 | } 71 | 72 | case "$codename" in 73 | "stretch") 74 | nginx_repo_add 75 | ;; 76 | "focal") 77 | nginx_repo_add 78 | ;; 79 | "bionic") 80 | nginx_repo_add 81 | ;; 82 | "xenial") 83 | nginx_repo_add 84 | ;; 85 | "buster") 86 | nginx_repo_add 87 | ;; 88 | *) 89 | echo "Distro: $codename" 90 | echo 'Warning: Could not figure out the distribution codename. Continuing to install Nginx from the OS.' 91 | ;; 92 | esac 93 | 94 | apt-get install -qq nginx &> /dev/null 95 | check_result $? 'Nginx: could not be installed.' 96 | 97 | backup_dir="/root/backups/etc-nginx-$(date +%F)" 98 | 99 | [ ! -d /root/backups ] && mkdir /root/backups 100 | 101 | if [ ! -d "$backup_dir" ]; then 102 | cp -a /etc $backup_dir 103 | fi 104 | 105 | # deploy wordpress-nginx repo 106 | if [ ! -d /root/git/wordpress-nginx ] ; then 107 | # git clone --quiet https://github.com/pothi/wordpress-nginx /root/git/wordpress-nginx 108 | # gitlab repo is more up-to-date 109 | git clone --quiet https://gitlab.com/pothi/wordpress-nginx /root/git/wordpress-nginx 110 | else 111 | cd /root/git/wordpress-nginx && git pull --quiet origin master && cd - &> /dev/null 112 | fi 113 | 114 | cp -a /root/git/wordpress-nginx/* /etc/nginx/ 115 | cp /etc/nginx/nginx-sample.conf /etc/nginx/nginx.conf 116 | [ ! -d /etc/nginx/sites-enabled ] && mkdir /etc/nginx/sites-enabled 117 | ln -s /etc/nginx/sites-available/default.conf /etc/nginx/sites-enabled/default.conf &> /dev/null 118 | 119 | # create dhparam 120 | if [ ! -f /etc/nginx/dhparam.pem ]; then 121 | $(which openssl) dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096 &> /dev/null 122 | sed -i 's:^# \(ssl_dhparam /etc/nginx/dhparam.pem;\)$:\1:' /etc/nginx/conf.d/ssl-common.conf 123 | fi 124 | 125 | nginx -t &> /dev/null && systemctl restart nginx &> /dev/null 126 | check_result $? 'Nginx: could not be restarted.' 127 | 128 | # unattended-upgrades 129 | unattended_file=/etc/apt/apt.conf.d/50unattended-upgrades 130 | case "$codename" in 131 | "focal") 132 | if ! grep -q '"origin=nginx,codename=${distro_codename}";' $unattended_file ; then 133 | sed -i -e '/^Unattended-Upgrade::Origins-Pattern/ a "origin=nginx,codename=${distro_codename}";' $unattended_file 134 | fi 135 | ;; 136 | "buster") 137 | if ! grep -q '"origin=nginx,codename=${distro_codename}";' $unattended_file ; then 138 | sed -i -e '/^Unattended-Upgrade::Origins-Pattern/ a "origin=nginx,codename=${distro_codename}";' $unattended_file 139 | fi 140 | ;; 141 | "stretch") 142 | if ! grep -q '"origin=nginx,codename=${distro_codename}";' $unattended_file ; then 143 | sed -i -e '/^Unattended-Upgrade::Origins-Pattern/ a "origin=nginx,codename=${distro_codename}";' $unattended_file 144 | fi 145 | ;; 146 | "xenial") 147 | if ! grep -q '"nginx:${distro_codename}";' $unattended_file ; then 148 | sed -i -e '/^Unattended-Upgrade::Allowed-Origins/ a "nginx:${distro_codename}";' $unattended_file 149 | fi 150 | ;; 151 | "bionic") 152 | if ! grep -q '"nginx:${distro_codename}";' $unattended_file ; then 153 | sed -i -e '/^Unattended-Upgrade::Allowed-Origins/ a "nginx:${distro_codename}";' $unattended_file 154 | fi 155 | ;; 156 | *) 157 | echo 'Warning: Could not figure out the distribution codename. Skipping unattended upgrade for nginx!' 158 | ;; 159 | esac 160 | 161 | echo ... done installing up Nginx! 162 | -------------------------------------------------------------------------------- /scripts/nvm-nodejs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | [ -f ~/.envrc ] && . ~/.envrc 7 | 8 | printf '%-72s' "Installing nodejs/npm..." 9 | if [ ! -s ~/scripts/nvm-install-script.sh ]; then 10 | curl -L -o ~/scripts/nvm-install-script.sh https://raw.githubusercontent.com/nvm-sh/nvm/$nvm_version/install.sh 11 | # Creates an issue with GIT 12 | # bash ~/scripts/nvm-install-script.sh 13 | # export NVM_DIR="~/.nvm" 14 | # [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # this loads nvm 15 | # nvm install --lts 16 | fi 17 | echo done. 18 | 19 | -------------------------------------------------------------------------------- /scripts/optional-installation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # variables 9 | 10 | # logging everything 11 | log_file=/root/log/wp-in-a-box.log 12 | exec > >(tee -a ${log_file} ) 13 | exec 2> >(tee -a ${log_file} >&2) 14 | 15 | local_wp_in_a_box_repo=/root/git/wp-in-a-box 16 | . /root/.envrc 17 | 18 | echo "Optional script started on (date & time): $(date +%c)" 19 | 20 | export DEBIAN_FRONTEND=noninteractive 21 | 22 | echo 23 | echo Updating the server... 24 | echo ---------------------- 25 | printf '%-72s' "Running apt-get upgrade..." 26 | apt-get -qq upgrade &> /dev/null 27 | echo done. 28 | printf '%-72s' "Running apt-get dist-upgrade..." 29 | apt-get -qq dist-upgrade &> /dev/null 30 | echo done. 31 | printf '%-72s' "Running apt-get autoremove..." 32 | apt-get -qq autoremove &> /dev/null 33 | echo done. 34 | echo 35 | 36 | . $local_wp_in_a_box_repo/scripts/email-mta-installation.sh 37 | echo 38 | 39 | echo Installing optional packages... 40 | echo ----------------------------------------------------------------------------- 41 | optional_packages="apt-file \ 42 | apache2-utils \ 43 | acl \ 44 | bc \ 45 | debian-goodies \ 46 | direnv \ 47 | duplicity \ 48 | gawk \ 49 | logwatch \ 50 | mailutils \ 51 | members \ 52 | molly-guard \ 53 | nmap \ 54 | plocate \ 55 | tree \ 56 | uptimed \ 57 | vim-scripts \ 58 | zip" 59 | 60 | for package in $optional_packages 61 | do 62 | if dpkg-query -s $package &> /dev/null 63 | then 64 | echo "$package is already installed" 65 | else 66 | printf '%-72s' "Installing ${package}..." 67 | apt-get -qq install $package &> /dev/null 68 | echo done. 69 | fi 70 | done 71 | echo ------------------------------------------------------------------------- 72 | echo ... done installing prerequisites! 73 | echo 74 | 75 | #--- Setup direnv ---# 76 | # if ! grep 'direnv' /root/.bashrc ; then 77 | # echo 'eval "$(direnv hook bash)"' >> /root/.bashrc 78 | # fi 79 | 80 | #--- Download and setup some helper tools ---# 81 | if [ ! -s /root/ps_mem.py ]; then 82 | printf '%-72s' "Downloading ps_mem.py script..." 83 | script_url=http://www.pixelbeat.org/scripts/ps_mem.py 84 | wget -q -O /root/ps_mem.py $script_url 85 | check_result $? 'ps_mem.py: error downloading the script.' 86 | chmod +x /root/ps_mem.py 87 | echo done. 88 | fi 89 | 90 | if [ ! -s /root/scripts/mysqltuner.pl ]; then 91 | printf '%-72s' "Downloading mysqlturner script..." 92 | script_url=https://raw.github.com/major/MySQltuner-perl/master/mysqltuner.pl 93 | wget -q -O /root/scripts/mysqltuner.pl $script_url 94 | check_result $? 'mysqltuner: error downloading the script.' 95 | chmod +x /root/scripts/mysqltuner.pl 96 | echo done. 97 | fi 98 | 99 | # Try https://github.com/BMDan/tuning-primer.sh 100 | if [ ! -s /root/scripts/tuning-primer.sh ]; then 101 | printf '%-72s' "Downloading tuning-primer script..." 102 | script_url=https://launchpad.net/mysql-tuning-primer/trunk/1.6-r1/+download/tuning-primer.sh 103 | wget -q -O /root/scripts/tuning-primer.sh $script_url 104 | check_result $? 'tuning-primer: error downloading the script.' 105 | chmod +x /root/scripts/tuning-primer.sh 106 | sed -i 's/\bjoin_buffer\b/&_size/' /root/scripts/tuning-primer.sh 107 | echo done. 108 | fi 109 | 110 | # depends on mysql & php installation 111 | . $local_wp_in_a_box_repo/scripts/pma-user-creation.sh 112 | echo 113 | . $local_wp_in_a_box_repo/scripts/redis.sh 114 | echo 115 | 116 | echo "Optional script ended on (date & time): $(date +%c)" 117 | -------------------------------------------------------------------------------- /scripts/php-installation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Usage as a standalone script: PHP_VER=8.0 bash php-installation.sh 4 | 5 | # programming env: these switches turn some bugs into errors 6 | # set -o errexit -o pipefail -o noclobber -o nounset 7 | 8 | export DEBIAN_FRONTEND=noninteractive 9 | 10 | # Defining return code check function 11 | check_result() { 12 | if [ $1 -ne 0 ]; then 13 | echo "Error: $2" 14 | exit $1 15 | fi 16 | } 17 | 18 | function install_php_fpm { 19 | version=$1 20 | PACKAGES="php${version}-fpm \ 21 | php${version}-mysql \ 22 | php${version}-gd \ 23 | php${version}-cli \ 24 | php${version}-xml \ 25 | php${version}-mbstring \ 26 | php${version}-soap \ 27 | php${version}-curl \ 28 | php${version}-zip \ 29 | php${version}-bcmath \ 30 | php${php_ver}-intl \ 31 | php${version}-imagick" 32 | 33 | if [ "$version" = "7.0" ] ; then 34 | apt-get -qq install "php${version}-mcrypt" &> /dev/null 35 | elif [ "$version" = "7.1" ] ; then 36 | apt-get -qq install "php${version}-mcrypt" &> /dev/null 37 | # echo 38 | # echo Note on mcrypt 39 | # echo -------------- 40 | # echo mycrypt is removed since PHP 7.2 41 | # echo Please check if any plugins or theme still use mcrypt by running... 42 | # echo 'find ~/wproot/wp-content/ -type f -name "*.php" -exec grep -inr mcrypt {} \;' 43 | # echo 44 | fi 45 | 46 | for package in ${PACKAGES} 47 | do 48 | if dpkg-query -W -f='${Status}' $package | grep -q "ok installed"; 49 | then 50 | echo "$package is already installed" 51 | else 52 | printf '%-72s' "Installing ${package}..." 53 | apt-get -qq install $package &> /dev/null 54 | echo done. 55 | fi 56 | done 57 | } 58 | 59 | # Variable/s 60 | # php_user 61 | # PHP_MAX_CHILDREN 62 | # MY_MEMCACHED_MEMORY 63 | # PM_METHOD 64 | # WP_ENVIRONMENT_TYPE # local, development, staging, production 65 | # BASE_NAME 66 | 67 | env_type=${WP_ENVIRONMENT_TYPE:-''} 68 | 69 | #TODO: 70 | # check for installation status of each package and then install individual packages. 71 | # change fpm value in Nginx conf.d/lb.conf and vhost entries! 72 | 73 | supplied_php_version=${PHP_VER:-""} 74 | 75 | local_wp_in_a_box_repo=/root/git/wp-in-a-box 76 | [ ! -d $local_wp_in_a_box_repo ] && git clone https://github.com/pothi/wp-in-a-box $local_wp_in_a_box_repo 77 | 78 | # get the variables 79 | [ -f /root/.envrc ] && . /root/.envrc 80 | 81 | PM_METHOD=ondemand 82 | 83 | user_mem_limit=${PHP_MEM_LIMIT:-""} 84 | if [ -z "$user_mem_limit" ]; then 85 | # let's be safe with a minmal value 86 | # echo 'Setting PHP memory limit to 256mb' 87 | user_mem_limit=256 88 | fi 89 | 90 | if [[ $env_type = "local" ]]; then 91 | sudo apt-get install software-properties-common 92 | sudo add-apt-repository --yes --update ppa:ondrej/php 93 | user_mem_limit=2048 94 | fi 95 | 96 | php_user=${DEV_USER:-""} 97 | if [ -z "$php_user" ]; then 98 | echo 'DEV_USER environmental variable is not found.' 99 | echo 'If you use a different variable name for your developer, please update the script or /root/.envrc file and re-run.' 100 | echo 'Developer env variable is not found. Exiting prematurely!'; exit 101 | fi 102 | 103 | max_children=${PHP_MAX_CHILDREN:-""} 104 | 105 | if [ -z "$max_children" ]; then 106 | # let's be safe with a minmal value 107 | sys_memory=$(free -m | grep -oP '\d+' | head -n 1) 108 | if (($sys_memory <= 600)) ; then 109 | max_children=4 110 | elif (($sys_memory <= 1600)) ; then 111 | max_children=6 112 | elif (($sys_memory <= 5600)) ; then 113 | max_children=10 114 | elif (($sys_memory <= 10600)) ; then 115 | PM_METHOD=static 116 | max_children=20 117 | elif (($sys_memory <= 20600)) ; then 118 | PM_METHOD=static 119 | max_children=40 120 | elif (($sys_memory <= 30600)) ; then 121 | PM_METHOD=static 122 | max_children=60 123 | elif (($sys_memory <= 40600)) ; then 124 | PM_METHOD=static 125 | max_children=80 126 | else 127 | PM_METHOD=static 128 | max_children=100 129 | fi 130 | fi 131 | 132 | if [[ $env_type = "local" ]]; then 133 | PM_METHOD=ondemand 134 | fi 135 | 136 | # php${php_version}-mysqlnd package is not found in Ubuntu 137 | 138 | if ! $(type 'codename' 2>/dev/null | grep -q 'function') 139 | then 140 | codename() { 141 | lsb_release_cli=$(which lsb_release) 142 | local codename="" 143 | if [ ! -z $lsb_release_cli ]; then 144 | codename=$($lsb_release_cli -cs) 145 | else 146 | codename=$(cat /etc/os-release | awk -F = '/VERSION_CODENAME/{print $2}') 147 | fi 148 | echo "$codename" 149 | } 150 | codename=$(codename) 151 | fi 152 | 153 | case "$codename" in 154 | "buster") 155 | system_php_version=7.3 156 | ;; 157 | "jammy") 158 | system_php_version=8.1 159 | ;; 160 | "focal") 161 | system_php_version=7.4 162 | ;; 163 | "bionic") 164 | system_php_version=7.2 165 | ;; 166 | "stretch") 167 | system_php_version=7.0 168 | ;; 169 | "xenial") 170 | system_php_version=7.0 171 | ;; 172 | *) 173 | echo 'Error: Could not figure out the distribution codename to install PHP. Exiting now!' 174 | exit 1 175 | ;; 176 | esac 177 | 178 | # TODO: If PHP_VER is supplied via commandline, it isn't overriden here. Thus PHP_VER is always 7.4 or whatever is defined in /root/.envrc 179 | php_version=${PHP_VER:-$system_php_version} 180 | # temp hack for the above todo 181 | [ ! -z $supplied_php_version ] && php_version=$supplied_php_version 182 | 183 | if ! grep -qw "$PHP_VER" /root/.envrc &> /dev/null ; then 184 | echo "export PHP_VER=$php_version" >> /root/.envrc 185 | fi 186 | 187 | echo; echo "Installing PHP $php_version..." 188 | echo -------------------------------------------------------------------------; echo 189 | 190 | fpm_ini_file=/etc/php/${php_version}/fpm/php.ini 191 | cli_ini_file=/etc/php/${php_version}/cli/php.ini 192 | pool_file=/etc/php/${php_version}/fpm/pool.d/${php_user}.conf 193 | 194 | ### Please do not edit below this line ### 195 | 196 | install_php_fpm $php_version 197 | 198 | # let's take a backup of config before modifing them 199 | datestamp="$(date +%F)" 200 | [ ! -d "/root/backups/etc-php-$datestamp" ] && cp -a /etc /root/backups/etc-php-$datestamp 201 | 202 | # echo 'Setting up memory limits for PHP...' 203 | 204 | ### ---------- php.ini modifications ---------- ### 205 | 206 | # for https://github.com/pothi/wp-in-a-box/issues/35 207 | echo 'Turning off logging of errors in php.ini...' 208 | sed -i -e '/^log_errors/ s/= On*/= Off/' $fpm_ini_file 209 | 210 | # sed -i '/cgi.fix_pathinfo \?=/ s/;\? \?\(cgi.fix_pathinfo \?= \?\)1/\10/' $fpm_ini_file # as per the note number 6 at https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/ 211 | # sed -i -e '/^max_execution_time/ s/=.*/= 300/' -e '/^max_input_time/ s/=.*/= 600/' $fpm_ini_file 212 | echo "Configuring memory limit to ${user_mem_limit}MB" 213 | sed -i -e '/^memory_limit/ s/=.*/= '$user_mem_limit'M/' $fpm_ini_file 214 | 215 | user_max_filesize=${PHP_MAX_FILESIZE:-64} 216 | echo "Configuring 'post_max_size' and 'upload_max_filesize' to ${user_max_filesize}MB..." 217 | sed -i -e '/^post_max_size/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 218 | sed -i -e '/^upload_max_filesize/ s/=.*/= '$user_max_filesize'M/' $fpm_ini_file 219 | 220 | # set max_input_vars to 5000 (from the default 1000) 221 | user_max_input_vars=${PHP_MAX_INPUT_VARS:-5000} 222 | echo "Configuring 'max_input_vars' to $user_max_input_vars (from the default 1000)..." 223 | sed -i '/max_input_vars/ s/;\? \?\(max_input_vars \?= \?\)[[:digit:]]\+/\1'$user_max_input_vars'/' $fpm_ini_file 224 | 225 | # Disable user.ini 226 | echo "Disabling user.ini..." 227 | sed -i -e '/^;user_ini.filename =$/ s/;//' $fpm_ini_file 228 | 229 | # Setup timezone 230 | user_timezone=${USER_TIMEZONE:-UTC} 231 | echo "Configuring timezone to $user_timezone ..." 232 | sed -i -e 's/^;date\.timezone =$/date.timezone = "'$user_timezone'"/' $fpm_ini_file 233 | 234 | echo "Turning off logging for cli to prevent warning messaging while running wp-cli..." 235 | # Turn off warning messages when running wp-cli 236 | sed -i -e '/^log_errors/ s/=.*/= Off/' $cli_ini_file 237 | 238 | # SESSION Handling 239 | # redis_pass= 240 | # [ -f /etc/redis/redis.conf ] && redis_pass=$(grep -w '^requirepass' /etc/redis/redis.conf | awk '{print $2}') 241 | # if [ ! -z $redis_pass ] ; then 242 | # echo 'Setting up sessions with redis...'; 243 | # sed -i -e '/^session.save_handler/ s/=.*/= redis/' $fpm_ini_file 244 | # sed -i -e '/^session.save_path/ s/.*/session.save_path = "tcp:\/\/127.0.0.1:6379?auth='$redis_pass'"/' $fpm_ini_file 245 | # fi 246 | 247 | ### ---------- pool-file modifications ---------- ### 248 | 249 | [ ! -f $pool_file ] && mv /etc/php/${php_version}/fpm/pool.d/www.conf $pool_file 250 | 251 | # echo 'Setting up the initial PHP user...' 252 | 253 | # Change default user 254 | sed -i -e 's/^\[www\]$/['$php_user']/' $pool_file 255 | sed -i -e 's/www-data/'$php_user'/' $pool_file 256 | # sed -i -e '/^\(user\|group\)/ s/=.*/= '$php_user'/' $pool_file 257 | # sed -i -e '/^listen.\(owner\|group\)/ s/=.*/= '$php_user'/' $pool_file 258 | 259 | sed -i -e '/^;listen.\(owner\|group\|mode\)/ s/^;//' $pool_file 260 | sed -i -e '/^listen.mode = / s/[0-9]\{4\}/0666/' $pool_file 261 | 262 | # echo 'Setting up the port / socket for PHP...' 263 | 264 | # Setup port / socket 265 | # sed -i '/^listen =/ s/=.*/= 127.0.0.1:9006/' $pool_file 266 | php_version_short=$(echo $php_version | sed 's/\.//') 267 | sed -i "/^listen =/ s:=.*:= /var/lock/php-fpm-${php_version_short}-${php_user}:" $pool_file 268 | sed -i "s:/var/lock/php-fpm.*;:/var/lock/php-fpm-${php_version_short}-${php_user};:" /etc/nginx/conf.d/lb.conf 269 | 270 | if [ ! -f /etc/nginx/conf.d/fpm-${php_version_short}.conf ]; then 271 | echo "upstream fpm${php_version_short} { server unix:/var/lock/php-fpm-${php_version_short}-dev; }" > /etc/nginx/conf.d/fpm-${php_version_short}.conf 272 | fi 273 | 274 | sed -i -e 's/^pm = .*/pm = '$PM_METHOD'/' $pool_file 275 | sed -i '/^pm.max_children/ s/=.*/= '$max_children'/' $pool_file 276 | 277 | # echo 'Setting up the processes...' 278 | 279 | #--- for dynamic PHP workers ---# 280 | PHP_MIN=$(expr $max_children / 10) 281 | # PHP_MAX=$(expr $max_children / 2) 282 | # PHP_DIFF=$(expr $PHP_MAX - $PHP_MIN) 283 | # PHP_START=$(expr $PHP_MIN + $PHP_DIFF / 2) 284 | 285 | # if [ "$max_children" != '' ]; then 286 | # sed -i '/^pm = dynamic/ s/=.*/= static/' $pool_file 287 | # sed -i '/^pm.max_children/ s/=.*/= '$max_children'/' $pool_file 288 | # sed -i '/^pm.start_servers/ s/=.*/= '$PHP_START'/' $pool_file 289 | # sed -i '/^pm.min_spare_servers/ s/=.*/= '$PHP_MIN'/' $pool_file 290 | # sed -i '/^pm.max_spare_servers/ s/=.*/= '$PHP_MAX'/' $pool_file 291 | # fi 292 | 293 | sed -i '/^;catch_workers_output/ s/^;//' $pool_file 294 | sed -i '/^;pm.process_idle_timeout/ s/^;//' $pool_file 295 | sed -i '/^;pm.max_requests/ s/^;//' $pool_file 296 | sed -i '/^;pm.status_path/ s/^;//' $pool_file 297 | sed -i '/^;ping.path/ s/^;//' $pool_file 298 | sed -i '/^;ping.response/ s/^;//' $pool_file 299 | 300 | # slow log 301 | home_basename=$(echo $php_user | awk -F _ '{print $1}') 302 | PHP_SLOW_LOG_PATH="\/home\/${home_basename}\/log\/slow-php.log" 303 | sed -i '/^;slowlog/ s/^;//' $pool_file 304 | sed -i '/^slowlog/ s/=.*$/ = '$PHP_SLOW_LOG_PATH'/' $pool_file 305 | sed -i '/^;request_slowlog_timeout/ s/^;//' $pool_file 306 | sed -i '/^request_slowlog_timeout/ s/= .*$/= 60/' $pool_file 307 | 308 | # automatic restart upon random failure - directly from http://tweaked.io/guide/nginx/ 309 | FPMCONF="/etc/php/${php_version}/fpm/php-fpm.conf" 310 | sed -i '/^;emergency_restart_threshold/ s/^;//' $FPMCONF 311 | sed -i '/^emergency_restart_threshold/ s/=.*$/= '$PHP_MIN'/' $FPMCONF 312 | sed -i '/^;emergency_restart_interval/ s/^;//' $FPMCONF 313 | sed -i '/^emergency_restart_interval/ s/=.*$/= 1m/' $FPMCONF 314 | sed -i '/^;process_control_timeout/ s/^;//' $FPMCONF 315 | sed -i '/^process_control_timeout/ s/=.*$/= 10s/' $FPMCONF 316 | 317 | # tweaking opcache 318 | # echo Tweaking opcache... 319 | if [ -f $local_wp_in_a_box_repo/config/php/mods-available/custom-opcache.ini ]; then 320 | cp $local_wp_in_a_box_repo/config/php/mods-available/custom-opcache.ini /etc/php/${php_version}/mods-available 321 | ln -s /etc/php/${php_version}/mods-available/custom-opcache.ini /etc/php/${php_version}/fpm/conf.d/99-custom-opcache.ini &> /dev/null 322 | ln -s /etc/php/${php_version}/mods-available/custom-opcache.ini /etc/php/${php_version}/cli/conf.d/99-custom-opcache.ini &> /dev/null 323 | fi 324 | 325 | echo 'Restarting PHP daemon...' 326 | 327 | /usr/sbin/php-fpm${php_version} -t &> /dev/null && systemctl restart php${php_version}-fpm &> /dev/null 328 | if [ $? -ne 0 ]; then 329 | echo 'PHP-FPM failed to restart. Please check your configs!'; exit 330 | else 331 | printf '\t\t\t%s\n' "... PHP-FPM was successfully restarted." 332 | fi 333 | 334 | echo 'Restarting Nginx...' 335 | 336 | /usr/sbin/nginx -t &> /dev/null && systemctl restart nginx &> /dev/null 337 | if [ $? -ne 0 ]; then 338 | echo 'Nginx failed to restart. Please check your configs! Exiting now!'; exit 339 | else 340 | # echo ... Nginx was successfully restarted. 341 | printf '\t\t\t%s\n' "... Nginx was successfully restarted." 342 | fi 343 | 344 | ### ---------- other misc tasks ---------- ### 345 | 346 | # restart php upon OOM or other failures 347 | # ref: https://stackoverflow.com/a/45107512/1004587 348 | # sed -i '/^\[Service\]/!b;:a;n;/./ba;iRestart=on-failure' /lib/systemd/system/php${php_version}-fpm.service 349 | # systemctl daemon-reload 350 | # check_result $? "Could not update /lib/systemd/system/php${php_version}-fpm.service file!" 351 | 352 | echo; echo ------------------------------------------------------------------------- 353 | echo "All done with PHP $php_version!" 354 | echo 355 | -------------------------------------------------------------------------------- /scripts/pma-installation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | # - PMA auto update script is downloaded and executed. 8 | # - A cron tab entry is in place to update PMA using the auto update script. 9 | 10 | # variables 11 | 12 | 13 | mkdir ~/{phpmyadmin,log} &> /dev/null 14 | 15 | curl -sL https://github.com/pothi/snippets/raw/main/misc/pma-auto-update.sh -o ~/pma-auto-update.sh 16 | chmod +x ~/pma-auto-update.sh 17 | 18 | ~/pma-auto-update.sh 19 | 20 | # setup cron to self-update phpmyadmin 21 | if ! $(crontab -l | grep -qw phpmyadmin) ; then 22 | ( crontab -l; echo; echo "# auto-update phpmyadmin - nightly" ) | crontab - 23 | ( crontab -l; echo '@daily ~/pma-auto-update.sh &> /dev/null' ) | crontab - 24 | fi 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /scripts/pma-user-creation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=2.0 4 | 5 | # programming env: these switches turn some bugs into errors 6 | # set -o errexit -o pipefail -o noclobber -o nounset 7 | 8 | # what's done here 9 | 10 | # variables 11 | # DEV_USER 12 | 13 | ###---------- Please do not edit below this line ----------### 14 | 15 | export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin 16 | export DEBIAN_FRONTEND=noninteractive 17 | 18 | local_wp_in_a_box_repo=/root/git/wp-in-a-box 19 | [ -f /root/.envrc ] && . /root/.envrc 20 | 21 | php_user=${WP_USERNAME:-""} 22 | if [ -z "$php_user" ]; then 23 | echo 'WP_USERNAME environmental variable is not found.' 24 | echo 'If you use a different variable name for your developer, please update the script or /root/.envrc file and re-run.' 25 | echo 'Developer env variable is not found. Exiting prematurely!'; exit 26 | fi 27 | 28 | export PMA_USER=pma 29 | export PMA_HOME=/var/www/pma 30 | export PMA_ENV=${PMA_HOME}/.envrc 31 | export PMA_TMP=${PMA_HOME}/phpmyadmin/tmp 32 | 33 | [ ! -d /var/www/pma ] && mkdir -p /var/www/pma 34 | 35 | useradd --home-dir $PMA_HOME $PMA_USER &> /dev/null 36 | chown ${PMA_USER} $PMA_HOME 37 | 38 | if ! command -v pwgen >/dev/null; then 39 | apt-get -qq install pwgen > /dev/null 40 | fi 41 | 42 | if [ ! -f "${PMA_ENV}" ]; then 43 | dbuser=pma$(pwgen -cns 5 1) 44 | dbpass=$(pwgen -cnsv 8 1) 45 | echo "export pma_db_user=$dbuser" > ${PMA_ENV} 46 | echo "export pma_db_pass=$dbpass" >> ${PMA_ENV} 47 | chmod 600 ${PMA_ENV} 48 | chown $PMA_USER ${PMA_ENV} 49 | . ${PMA_ENV} 50 | fi 51 | 52 | mysql -e "CREATE DATABASE phpmyadmin" &> /dev/null 53 | mysql -e "CREATE USER $pma_db_user@localhost IDENTIFIED BY '$pma_db_pass'" &> /dev/null 54 | mysql -e "GRANT ALL PRIVILEGES ON phpmyadmin.* TO $pma_db_user@localhost" &> /dev/null 55 | 56 | [ -z $local_wp_in_a_box_repo ] && local_wp_in_a_box_repo=/root/git/wp-in-a-box 57 | # sudo -H -u $PMA_USER bash $local_wp_in_a_box_repo/scripts/pma-installation.sh 58 | cp $local_wp_in_a_box_repo/scripts/pma-installation.sh $PMA_HOME/ 59 | chown $PMA_USER $PMA_HOME/pma-installation.sh 60 | runuser -u $PMA_USER bash ${PMA_HOME}/pma-installation.sh 61 | rm ${PMA_HOME}/pma-installation.sh 62 | 63 | [ ! -d ${PMA_TMP} ] && mkdir ${PMA_TMP} 64 | # PMA_TMP must be owned by the user that runs PHP. 65 | # In our case, PHP is run as $DEV_USER 66 | chown ${php_user}:${php_user} ${PMA_TMP} 67 | -------------------------------------------------------------------------------- /scripts/post-install-bionic.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # variables 9 | 10 | export DEBIAN_FRONTEND=noninteractive 11 | 12 | echo Bionic specific changes: 13 | echo ------------------------ 14 | echo -n 'Installing certbot... ' 15 | apt-get install -qq certbot &> /dev/null 16 | echo 'done.' 17 | 18 | #-- For Redis ---# 19 | # one-time process 20 | echo never > /sys/kernel/mm/transparent_hugepage/enabled 21 | # to retain the above modification upon reboot 22 | if ! grep -q transparent_hugepage /etc/rc.local &> /dev/null ; then 23 | echo >> /etc/rc.local 24 | echo '# for redis - see https://github.com/pothi/wp-in-a-box/issues/51#issuecomment-343657080' >> /etc/rc.local 25 | echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >> /etc/rc.local 26 | echo >> /etc/rc.local 27 | fi 28 | -------------------------------------------------------------------------------- /scripts/post-install-buster.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # variables 9 | 10 | export DEBIAN_FRONTEND=noninteractive 11 | 12 | echo Bionic specific changes: 13 | echo ------------------------ 14 | echo -n 'Installing certbot... ' 15 | apt-get install -qq certbot &> /dev/null 16 | 17 | local_wp_in_a_box_repo=/root/git/wp-in-a-box 18 | 19 | [ -f "${local_wp_in_a_box_repo}/snippets/ssl/nginx-restart.sh" ] && { 20 | cp ${local_wp_in_a_box_repo}/snippets/ssl/nginx-restart.sh /etc/letsencrypt/renewal-hooks/deploy/ 21 | chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-restart.sh 22 | } 23 | 24 | echo 'done.' 25 | 26 | -------------------------------------------------------------------------------- /scripts/post-install-stretch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # variables 9 | 10 | export DEBIAN_FRONTEND=noninteractive 11 | 12 | # post-install script for Debian 13 | 14 | apt-get install certbot -t stretch-backports -q -y 15 | if [ "$?" -ne "0" ]; then 16 | echo 'deb http://deb.debian.org/debian stretch-backports main' > /etc/apt/sources.list.d/backports.list 17 | apt-get update -qq 18 | apt-get install certbot -t stretch-backports -q -y 19 | if [ "$?" -ne "0" ]; then 20 | echo 'Something went wrong while installing Certbot using backports. Please check the logs.' 21 | fi 22 | fi 23 | -------------------------------------------------------------------------------- /scripts/post-install-xenial.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # variables 9 | 10 | 11 | # post install script for Ubuntu Xenial 12 | 13 | export DEBIAN_FRONTEND=noninteractive 14 | add-apt-repository ppa:certbot/certbot -y 15 | apt-get update -q -y 16 | apt-get install certbot -q -y 17 | 18 | #-- For Redis ---# 19 | # one-time process 20 | echo never > /sys/kernel/mm/transparent_hugepage/enabled 21 | # to retain the above modification upon reboot 22 | if ! $(grep -q transparent_hugepage /etc/rc.local) ; then 23 | echo >> /etc/rc.local 24 | echo '# for redis - see https://github.com/pothi/wp-in-a-box/issues/51#issuecomment-343657080' >> /etc/rc.local 25 | echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >> /etc/rc.local 26 | echo >> /etc/rc.local 27 | fi 28 | -------------------------------------------------------------------------------- /scripts/redis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | export DEBIAN_FRONTEND=noninteractive 7 | 8 | # what's done here 9 | # 1. install redis, redis for PHP 10 | # 2. configure redis and restart it 11 | # 3. configure php and restart it 12 | # 4. tweak sysctl for redis 13 | 14 | # variables 15 | redis_maxmemory_policy='allkeys-lru' 16 | redis_conf_file='/etc/redis/redis.conf' 17 | redis_sysctl_file='/etc/sysctl.d/60-redis-local.conf' 18 | 19 | . /root/.envrc 20 | 21 | php_version=${PHP_VER:-7.4} 22 | 23 | restart_redis='no' 24 | 25 | if [ -f '/etc/redis/redis.conf' ]; then 26 | redis_pass=$(grep -w '^requirepass' /etc/redis/redis.conf | awk '{print $2}') 27 | fi 28 | if [ -z "$redis_pass" ]; then 29 | redis_pass=$(pwgen -cns 20 1) 30 | 31 | restart_redis='yes' 32 | 33 | fi 34 | 35 | # php_version from php-installation.php 36 | fpm_ini_file=/etc/php/${php_version}/fpm/php.ini 37 | 38 | echo -n 'Installing redis... ' 39 | apt-get install -qq redis-server &> /dev/null 40 | echo 'done.' 41 | 42 | if apt-cache show php${php_version}-redis &> /dev/null ; then 43 | redis_php_package="php${php_version}-redis" 44 | else 45 | redis_php_package=php-redis 46 | fi 47 | 48 | echo -n 'Installing redis for PHP... ' 49 | apt-get install -qq ${redis_php_package} &> /dev/null 50 | echo 'done.' 51 | 52 | echo Tweaking redis cache... 53 | 54 | # calculate memory to use for redis 55 | sys_memory=$(free -m | grep -oP '\d+' | head -n 1) 56 | redis_memory=$(($sys_memory / 32)) 57 | sed -i -e 's/^#\? \?\(maxmemory \).*$/\1'$redis_memory'm/' $redis_conf_file 58 | 59 | # change the settings for maxmemory-policy 60 | sed -i -e 's/^#\? \?\(maxmemory\-policy\).*$/\1 '$redis_maxmemory_policy'/' $redis_conf_file 61 | 62 | # set password 63 | sed -i -e 's/^#\? \?\(requirepass\).*$/\1 '$redis_pass'/' $redis_conf_file 64 | 65 | # create / overwrite and append our custom values in it 66 | # ref: https://www.victordodon.com/to-clobber-or-to-noclobber/ 67 | # ref: https://superuser.com/a/1498407/142306 68 | printf "vm.overcommit_memory = 1\n" >| $redis_sysctl_file 69 | printf "net.core.somaxconn = 1024\n" >> $redis_sysctl_file 70 | 71 | # Load settings from the redis sysctl file 72 | sysctl -p $redis_sysctl_file 73 | 74 | # restart redis 75 | if [ "yes" == "$restart_redis" ] ; then 76 | /bin/systemctl restart redis-server 77 | else 78 | # due to incomplete $restart_redis validation 79 | /bin/systemctl restart redis-server 80 | fi 81 | 82 | echo '... done tweaking redis cache.' 83 | 84 | # SESSION Handling 85 | echo 'Setting up PHP sessions to use redis... ' 86 | if [ ! $(grep '^session.save_handler = redis$' $fpm_ini_file) ] ; then 87 | sed -i -e '/^session.save_handler/ s/=.*/= redis/' $fpm_ini_file 88 | fi 89 | sed -i -e '/^;session.save_path/ s/^;//' $fpm_ini_file 90 | sed -i -e '/^session.save_path/ s/.*/session.save_path = "tcp:\/\/127.0.0.1:6379?auth='$redis_pass'"/' $fpm_ini_file 91 | 92 | /usr/sbin/php-fpm${php_version} -t &> /dev/null && systemctl restart php${php_version}-fpm &> /dev/null 93 | if [ "$?" != 0 ]; then 94 | echo 'PHP-FPM failed to restart. Please check your configs!'; exit 95 | fi 96 | 97 | echo '... done setting up PHP sessions to use redis!' 98 | 99 | # one-time process 100 | # echo never > /sys/kernel/mm/transparent_hugepage/enabled 101 | # to retain the above modification upon reboot 102 | # if ! grep -q transparent_hugepage /etc/rc.local &> /dev/null ; then 103 | # echo >> /etc/rc.local 104 | # echo '# for redis - see https://github.com/pothi/wp-in-a-box/issues/51#issuecomment-343657080' >> /etc/rc.local 105 | # echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >> /etc/rc.local 106 | # echo >> /etc/rc.local 107 | # fi 108 | -------------------------------------------------------------------------------- /scripts/restrict-wp-user-to-sftp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # variables 9 | 10 | 11 | # Variables - you may send these as command line options 12 | # wp_user 13 | 14 | local_wp_in_a_box_repo=/root/git/wp-in-a-box 15 | . /root/.envrc 16 | 17 | echo 'Creating a WP username to login via SFTP...' 18 | 19 | wp_user=${WP_USERNAME:-""} 20 | if [ "$wp_user" == "" ]; then 21 | # create SFTP username automatically 22 | wp_user="wp_$(pwgen -Av 9 1)" 23 | echo "export WP_USER=$wp_user" >> /root/.envrc 24 | fi 25 | 26 | home_basename=$(echo $wp_user | awk -F _ '{print $1}') 27 | 28 | #--- please do not edit below this file ---# 29 | 30 | SSHD_CONFIG='/etc/ssh/sshd_config' 31 | 32 | if [ ! -d "/home/${home_basename}" ]; then 33 | useradd --shell=/bin/bash -m --home-dir /home/${home_basename} $wp_user 34 | 35 | groupadd ${home_basename} 36 | 37 | # "wp" is meant for SFTP only user/s 38 | gpasswd -a $wp_user ${home_basename} &> /dev/null 39 | 40 | chown root:root /home/${home_basename} 41 | chmod 755 /home/${home_basename} 42 | 43 | #-- allow the user to login to the server --# 44 | # older way of doing things by appending it to AllowUsers directive 45 | # if ! grep "$wp_user" ${SSHD_CONFIG} &> /dev/null ; then 46 | # sed -i '/AllowUsers/ s/$/ '$wp_user'/' ${SSHD_CONFIG} 47 | # fi 48 | # latest way of doing things 49 | # ref: https://knowledgelayer.softlayer.com/learning/how-do-i-permit-specific-users-ssh-access 50 | # groupadd –r sshusers 51 | 52 | # if AllowGroups line doesn't exist, insert it only once! 53 | # if ! grep -i "AllowGroups" ${SSHD_CONFIG} &> /dev/null ; then 54 | # echo ' 55 | # # allow users within the (system) group "sshusers" 56 | # AllowGroups sshusers 57 | # ' >> ${SSHD_CONFIG} 58 | # fi 59 | 60 | # add new users into the 'sshusers' now 61 | # usermod -a -G sshusers ${wp_user} 62 | 63 | # if the text 'match group ${home_basename}' isn't found, then 64 | # insert it only once 65 | if ! grep -q "Match group ${home_basename}" "${SSHD_CONFIG}" &> /dev/null ; then 66 | # remove the existing subsystem 67 | sed -i 's/^Subsystem/### &/' ${SSHD_CONFIG} 68 | 69 | # add new subsystem 70 | echo " 71 | # setup internal SFTP 72 | Subsystem sftp internal-sftp 73 | Match group ${home_basename} 74 | ChrootDirectory %h 75 | PasswordAuthentication yes 76 | X11Forwarding no 77 | AllowTcpForwarding no 78 | ForceCommand internal-sftp 79 | " >> ${SSHD_CONFIG} 80 | 81 | fi # /Match group ${home_basename} 82 | 83 | # echo 'Testing the modified SSH config' 84 | # the following didn't work 85 | # sshd –t 86 | # /usr/sbin/sshd -t 87 | # if [ "$?" != 0 ]; then 88 | # echo 'Something is messed up in the SSH config file' 89 | # echo 'Please re-run after fixing errors' 90 | # echo "See the logfile ${log_file} for details of the error" 91 | # echo 'Exiting pre-maturely' 92 | # exit 1 93 | # else 94 | # echo 'Cool. Things seem fine.' 95 | echo 'Restarting SSH daemon...' 96 | systemctl restart sshd &> /dev/null 97 | if [ "$?" != 0 ]; then 98 | echo 'Something went wrong while creating SFTP user! See below...'; echo; echo; 99 | systemctl status sshd 100 | else 101 | echo ...SSH daemon restarted! 102 | fi 103 | # fi # end of sshd -t check 104 | 105 | else 106 | echo "the default directory /home/${home_basename} already exists!" 107 | # exit 1 108 | fi # end of if ! -d "/home/${home_basename}" - whoops 109 | 110 | wp_pass=${WP_PASSWORD:-""} 111 | if [ "$wp_pass" == "" ]; then 112 | wp_pass=$(pwgen -cns 12 1) 113 | echo "export WP_PASSWORD=$wp_pass" >> /root/.envrc 114 | 115 | echo "$wp_user:$wp_pass" | chpasswd 116 | fi 117 | 118 | # cd $local_wp_in_a_box_repo/scripts/ &> /dev/null 119 | # sudo -H -u $wp_user bash nvm-nodejs.sh 120 | # cd - &> /dev/null 121 | 122 | echo ...done setting up SFTP username for Web Developer! 123 | -------------------------------------------------------------------------------- /scripts/server-admin-creation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # Variables - you may set the following in envrc file 9 | # ssh_user 10 | 11 | . /root/.envrc 12 | ssh_user=${ADMIN_USERNAME:-""} 13 | 14 | echo "Creating a 'server admin' user..." 15 | 16 | if [ "$ssh_user" == "" ]; then 17 | # create SSH username automatically 18 | ssh_user="admin_$(pwgen -Av 6 1)" 19 | echo "export ADMIN_USERNAME=$ssh_user" >> /root/.envrc 20 | fi 21 | 22 | admin_basename=$(echo $ssh_user | awk -F _ '{print $1}') 23 | 24 | SSHD_CONFIG='/etc/ssh/sshd_config' 25 | 26 | if ! grep -qw ssh_users $SSHD_CONFIG ; then 27 | groupadd ssh_users &> /dev/null 28 | 29 | echo ' 30 | Match group ssh_users 31 | PasswordAuthentication yes 32 | ' >> $SSHD_CONFIG 33 | 34 | echo 'Restarting SSH daemon...' 35 | systemctl restart sshd &> /dev/null 36 | if [ $? -ne 0 ]; then 37 | echo 'Something went wrong while creating SSH user! See below...'; echo; echo; 38 | systemctl status sshd 39 | else 40 | echo ... SSH Daemon restarted! 41 | fi 42 | fi 43 | 44 | if [ ! -d "/home/${ssh_user}" ]; then 45 | # useradd -m $ssh_user 46 | 47 | useradd --shell=/bin/bash -m --home-dir /home/${admin_basename} $ssh_user 48 | 49 | # groupadd ${admin_basename} 50 | 51 | echo "${ssh_user} ALL=(ALL) NOPASSWD:ALL"> /etc/sudoers.d/$ssh_user 52 | chmod 400 /etc/sudoers.d/$ssh_user 53 | 54 | gpasswd -a $ssh_user ssh_users &> /dev/null 55 | else 56 | echo "Note: The default directory /home/${admin_basename} already exists!" 57 | echo "Note: The user '${ssh_user}' already exists" 58 | fi 59 | 60 | ssh_pass=${ADMIN_PASSWORD:-""} 61 | if [ "$ssh_pass" == "" ]; then 62 | ssh_pass=$(pwgen -cns 12 1) 63 | 64 | echo "$ssh_user:$ssh_pass" | chpasswd 65 | echo "export ADMIN_PASSWORD=$ssh_pass" >> /root/.envrc 66 | fi 67 | 68 | 69 | if [ ! -f /home/${admin_basename}/.ssh/authorized_keys ]; then 70 | [ ! -d /home/${admin_basename}/.ssh ] && mkdir /home/${admin_basename}/.ssh && chmod 700 /home/${admin_basename}/.ssh 71 | [ -f /root/.ssh/authorized_keys ] && cp /root/.ssh/authorized_keys /home/${admin_basename}/.ssh/authorized_keys 72 | chown -R $ssh_user:$ssh_user /home/${admin_basename}/.ssh 73 | fi 74 | 75 | echo ...done setting up SSH user! 76 | -------------------------------------------------------------------------------- /scripts/swap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version=2.0 4 | 5 | # based on https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-22-04 6 | 7 | # programming env: these switches turn some bugs into errors 8 | # set -o errexit -o pipefail -o noclobber -o nounset 9 | 10 | # references: 11 | # https://stackoverflow.com/q/257844/1004587 12 | # https://askubuntu.com/q/1017309/65814 13 | 14 | # what's done here 15 | 16 | 17 | is_user_root() { [ "${EUID:-$(id -u)}" -eq 0 ]; } 18 | [ is_user_root ] || { echo 'You must be root or have sudo privilege to run this script. Exiting now.'; exit 1; } 19 | 20 | [ -f "$HOME/.envrc" ] && source ~/.envrc 21 | 22 | # variables 23 | swap_file='/swapfile' 24 | swap_size=${SWAP_SIZE:-'1G'} 25 | swap_sysctl_file='/etc/sysctl.d/60-swap-local.conf' 26 | sleep_time_between_tasks=2 27 | 28 | # take a backup before making changes 29 | [ -d ~/backups ] || mkdir ~/backups 30 | [ -f "$HOME/backups/fstab-$(date +%F)" ] || cp /etc/fstab ~/backups/fstab-"$(date +%F)" 31 | 32 | # helper function to exit upon non-zero exit code of a command 33 | # usage some_command; check_result $? 'some_command failed' 34 | if ! $(type 'check_result' 2>/dev/null | grep -q 'function') ; then 35 | check_result() { 36 | if [ "$1" -ne 0 ]; then 37 | echo -e "\nError: $2. Exiting!\n" 38 | exit "$1" 39 | fi 40 | } 41 | fi 42 | 43 | create_swap() { 44 | return 45 | } 46 | 47 | remove_swap() { 48 | swapoff "$swap_file" 49 | 50 | # Remove swap file 51 | [ -f "$swap_file" ] && rm "$swap_file" 52 | 53 | # Remove fstab entry 54 | if grep -q swap /etc/fstab >/dev/null ; then 55 | sed -i '/swap/d' /etc/fstab 56 | fi 57 | 58 | [ -f "$swap_sysctl_file" ] && rm "$swap_sysctl_file" 59 | service procps force-reload 60 | check_result $? "Error reloading procps!" 61 | } 62 | 63 | # Parse Flags 64 | parse_args() { 65 | while [[ $# -gt 0 ]]; do 66 | key="$1" 67 | 68 | case $key in 69 | -s | --size) 70 | swap_size=$2 71 | shift 72 | shift 73 | ;; 74 | --force-install | --force-no-brew) 75 | shift 76 | ;; 77 | -r | --remove) 78 | remove_swap 79 | exit 80 | ;; 81 | -v | --version) 82 | echo "Version: $version" 83 | exit 84 | ;; 85 | *) 86 | echo "Unrecognized argument $key" 87 | exit 1 88 | ;; 89 | esac 90 | done 91 | } 92 | 93 | parse_args "$@" 94 | 95 | # create swap if unavailable 96 | is_swap_enabled=$(free | grep -iw swap | awk {'print $2'}) # 0 means no swap 97 | if [ "$is_swap_enabled" -eq 0 ]; then 98 | printf '%-72s' 'Creating and setting up Swap...' 99 | # echo ----------------------------------------------------------------------------- 100 | 101 | # check for swap file 102 | if [ ! -f $swap_file ]; then 103 | # on a desktop, we may use fdisk to create a partition to be used as swap 104 | fallocate -l "$swap_size" "$swap_file" >/dev/null 105 | check_result $? "Error: fallocate failed!" 106 | fi 107 | 108 | # only root should be able to read it or / and write into it 109 | chmod 600 $swap_file 110 | 111 | # mark a file / partition as swap 112 | mkswap $swap_file >/dev/null 113 | check_result $? 'mkswap failed.' 114 | 115 | # enable swap 116 | # printf '%-72s' "Waiting for swap file to get ready..." 117 | # sleep $sleep_time_between_tasks 118 | # echo done. 119 | 120 | swapon "$swap_file" 121 | check_result $? "Error executing 'swapon $swap_file'. Exiting!" 122 | 123 | # display summary of swap (only for logging purpose) 124 | # swapon --show 125 | # swapon -s 126 | 127 | # to make the above changes permanent 128 | # enable swap upon boot 129 | if ! grep -qw swap /etc/fstab ; then 130 | echo "$swap_file none swap sw 0 0" >> /etc/fstab 131 | fi 132 | 133 | # fine-tune swap 134 | if [ ! -f $swap_sysctl_file ]; then 135 | echo -e "# Ref: https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-22-04\n" > $swap_sysctl_file 136 | echo 'vm.swappiness=10' >> $swap_sysctl_file 137 | echo 'vm.vfs_cache_pressure = 50' >> $swap_sysctl_file 138 | fi 139 | 140 | # apply changes 141 | # as per /etc/sysctl.d/README.sysctl 142 | if ! service procps force-reload ; then 143 | echo Error reloading procps! 144 | fi 145 | # alternative way 146 | # sysctl -p $swap_sysctl_file 147 | 148 | # echo ----------------------------------------------------------------------------- 149 | echo done! 150 | fi 151 | 152 | # TODO: Setup alert if swap is used 153 | # Not necessary when we use something like DO's built-in monitoring that can alert of memory goes beyond a limit. 154 | 155 | -------------------------------------------------------------------------------- /scripts/test-env-creation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | # creation of a test user and password. 8 | # adding test user to sftpusers group 9 | # restarting ssh server 10 | 11 | # variables 12 | # TEST_USER 13 | # TEST_PASS 14 | 15 | . /root/.envrc 16 | 17 | echo 'Creating a "test" user to login via SFTP...' 18 | 19 | test_user=${TEST_USER:-""} 20 | if [ "$test_user" == "" ]; then 21 | # create SFTP username automatically 22 | test_user="test_$(pwgen -Av 7 1)" 23 | echo "export TEST_USER=$test_user" >> /root/.envrc 24 | fi 25 | 26 | test_basename=$(echo $test_user | awk -F _ '{print $1}') 27 | 28 | #--- please do not edit below this file ---# 29 | 30 | if [ ! -d "/home/${test_basename}" ]; then 31 | useradd --shell=/bin/bash -m --home-dir /home/${test_basename} $test_user 32 | 33 | gpasswd -a $test_user sftpusers 34 | 35 | chown root:root /home/${test_basename} 36 | chmod 755 /home/${test_basename} 37 | 38 | echo 'Restarting SSH daemon...' 39 | systemctl restart sshd 40 | if [ "$?" != 0 ]; then 41 | echo 'Something went wrong while creating SFTP user! See below...'; echo; echo; 42 | systemctl status sshd 43 | else 44 | echo ...SSH daemon restarted! 45 | fi 46 | 47 | else 48 | echo "the default directory /home/${test_basename} already exists!" 49 | # exit 1 50 | fi # end of if ! -d "/home/${test_basename}" 51 | 52 | test_pass=${TEST_PASS:-""} 53 | if [ "$test_pass" == "" ]; then 54 | test_pass=$(pwgen -cns 12 1) 55 | echo "export TEST_PASS=$test_pass" >> /root/.envrc 56 | 57 | echo "$test_user:$test_pass" | chpasswd 58 | fi 59 | 60 | echo ...done setting up SFTP username for test env! 61 | 62 | -------------------------------------------------------------------------------- /scripts/unattended-reboots.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version=2.0 4 | 5 | # Careful when rebooting a server, unattended! 6 | 7 | # programming env: these switches turn some bugs into errors 8 | set -o errexit -o pipefail -o noclobber -o nounset 9 | 10 | # what's done here 11 | 12 | # variables 13 | REBOOT_TIME="03:45" 14 | 15 | [ "${EUID:-$(id -u)}" -eq 0 ] || { echo 'You must be root or have sudo privilege to run this script. Exiting now.'; exit 1; } 16 | 17 | # take a backup before making changes 18 | [ -d ~/backups ] || mkdir ~/backups 19 | [ -f "$HOME/backups/apt.conf.d-$(date +%F)" ] || cp -a /etc/apt/apt.conf.d ~/backups/apt.conf.d-"$(date +%F)" 20 | 21 | # we used ":" in sed in unattended-upgrades.sh file. 22 | # here, since reboot time has ":" in it, we can't use ":" in sed as separator. 23 | printf '%-72s' "Setting up unattended reboots..." 24 | 25 | un_up_file=/etc/apt/apt.conf.d/50unattended-upgrades 26 | sed -i '/Automatic\-Reboot/ s_^//U_U_' $un_up_file 27 | sed -i '/Automatic\-Reboot/ s_".*"_"true"_' $un_up_file 28 | 29 | sed -i '/Automatic\-Reboot\-WithUsers/ s_^//U_U_' $un_up_file 30 | sed -i '/Automatic\-Reboot\-WithUsers/ s_".*"_"false"_' $un_up_file 31 | 32 | sed -i '/Automatic\-Reboot\-Time/ s_^//U_U_' $un_up_file 33 | sed -i '/Automatic\-Reboot\-Time/ s_".*"_"'$REBOOT_TIME'"_' $un_up_file 34 | 35 | echo done. 36 | 37 | 38 | -------------------------------------------------------------------------------- /scripts/unattended-upgrades.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version=2.0 4 | 5 | # programming env: these switches turn some bugs into errors 6 | # set -o errexit -o pipefail -o noclobber -o nounset 7 | 8 | export DEBIAN_FRONTEND=noninteractive 9 | 10 | # what's done here 11 | 12 | # variables 13 | 14 | [ -f ~/.envrc ] && source ~/.envrc 15 | admin_email=${ADMIN_EMAIL:-"root@localhost"} 16 | 17 | [ "${EUID:-$(id -u)}" -eq 0 ] || { echo 'You must be root or have sudo privilege to run this script. Exiting now.'; exit 1; } 18 | 19 | # take a backup before making changes 20 | [ -d ~/backups ] || mkdir ~/backups 21 | [ -f "$HOME/backups/apt.conf.d-$(date +%F)" ] || cp -a /etc/apt/apt.conf.d ~/backups/apt.conf.d-"$(date +%F)" 22 | 23 | printf '%-72s' "Setting up unattended upgrades..." 24 | 25 | #--- Changes in /etc/apt/apt.conf.d/20auto-upgrades ---# 26 | auto_up_file=/etc/apt/apt.conf.d/20auto-upgrades 27 | echo 'APT::Periodic::Update-Package-Lists "1";' >| $auto_up_file 28 | echo 'APT::Periodic::Unattended-Upgrade "1";' >> $auto_up_file 29 | 30 | #--- Changes in /etc/apt/apt.conf.d/50unattended-upgrades ---# 31 | un_up_file=/etc/apt/apt.conf.d/50unattended-upgrades 32 | 33 | # Change #1 - Email address 34 | sed -i '/Mail / s:^//U:U:' $un_up_file 35 | sed -i '/Mail / s:".*":"'"$admin_email"'":' $un_up_file 36 | 37 | # Change #2 - When to send the email report 38 | # Set this value to one of: 39 | # "always", "only-on-error" or "on-change" 40 | # Applicable from Ubuntu 22.04 onwards 41 | mail_report_reason=only-on-error 42 | sed -i '/MailReport/ s:^//U:U:' $un_up_file 43 | sed -i '/MailReport/ s:".*":"'$mail_report_reason'":' $un_up_file 44 | # Change #2.1 - compatibility with older versions Ubuntu 20.04 or below 45 | # it is either true or false (default false) 46 | sed -i '/MailOnlyOnError/ s:^//U:U:' $un_up_file 47 | sed -i '/MailOnlyOnError/ s:".*":"true":' $un_up_file 48 | 49 | # Change #3 - Remove unused kernel 50 | sed -i '/Remove\-Unused\-Kernel\-Packages/ s:^//U:U:' $un_up_file 51 | sed -i '/Remove\-Unused\-Kernel\-Packages/ s:".*":"true":' $un_up_file 52 | 53 | # Change #4 - apt-get autoremove -y 54 | sed -i '/Remove\-Unused\-Dependencies/ s:^//U:U:' $un_up_file 55 | sed -i '/Remove\-Unused\-Dependencies/ s:".*":"true":' $un_up_file 56 | 57 | echo done. 58 | 59 | -------------------------------------------------------------------------------- /scripts/wp-cli-install.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # https://wp-cli.org/#installing 4 | 5 | # set -x 6 | 7 | # to capture non-zero exit code in the pipeline 8 | set -o pipefail 9 | 10 | # check root user 11 | # https://stackoverflow.com/a/52586842/1004587 12 | # also see https://stackoverflow.com/q/3522341/1004587 13 | is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; } 14 | if is_user_root; then 15 | BinDir=/usr/local/bin 16 | bash_completion_dir=/etc/bash_completion.d 17 | else 18 | BinDir=~/.local/bin 19 | # https://serverfault.com/a/968369/102173 20 | bash_completion_dir=${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions 21 | # attempt to create BinDir and bash_completion_dir 22 | [ -d $BinDir ] || mkdir -p $BinDir 23 | if [ "$?" -ne "0" ]; then 24 | echo >&2 "BinDir is not found at $BinDir. This script can't create it, either!" 25 | echo >&2 'You may create it manually and re-run this script.' 26 | exit 1 27 | fi 28 | [ -d "$bash_completion_dir" ] || mkdir -p "$bash_completion_dir" 29 | if [ "$?" -ne "0" ]; then 30 | echo >&2 "[Warn] bash_completion_dir is not found at $bash_completion_dir. This script can't create it, either!" 31 | echo >&2 'You may create it manually and re-run this script.' 32 | fi 33 | fi 34 | 35 | export PATH=~/bin:~/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin 36 | 37 | wp_cli="${BinDir}/wp" 38 | #--- Install wp cli ---# 39 | if [ ! -s "$wp_cli" ]; then 40 | printf '%-72s' "Downloading WP CLI..." 41 | wp_cli_url=https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 42 | if ! curl -LSs -o "$wp_cli" $wp_cli_url; then 43 | echo >&2 'wp-cli: error downloading wp-cli.' 44 | exit 1 45 | fi 46 | chmod +x "$wp_cli" 47 | 48 | echo done. 49 | fi 50 | 51 | # wp cli bash completion 52 | if [ ! -s "$bash_completion_dir/wp-completion.bash" ]; then 53 | if ! curl -LSs -o "${bash_completion_dir}/wp-completion.bash" https://github.com/wp-cli/wp-cli/raw/main/utils/wp-completion.bash; then 54 | echo >&2 'wp-cli: error downloading bash completion script.' 55 | fi 56 | fi 57 | 58 | #--- cron: auto-update wp-cli ---# 59 | if ! grep -qF "$wp_cli" "/var/spool/cron/crontabs/$USER" 2>/dev/null 60 | then 61 | ( crontab -l 2>/dev/null; echo; echo "@daily $wp_cli cli update --yes > /dev/null" ) | crontab - 62 | echo 'A new cron entry is created to update daily, if an update is available.' 63 | else 64 | echo 'A cron entry is already in place!' 65 | fi 66 | -------------------------------------------------------------------------------- /scripts/wp-user-creation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # programming env: these switches turn some bugs into errors 4 | # set -o errexit -o pipefail -o noclobber -o nounset 5 | 6 | # what's done here 7 | 8 | # variables 9 | 10 | # Variables - you may send these as command line options 11 | # wp_user 12 | 13 | . /root/.envrc 14 | 15 | echo 'Creating a WP username...' 16 | 17 | wp_user=${WP_USERNAME:-""} 18 | if [ "$wp_user" == "" ]; then 19 | # create SFTP username automatically 20 | wp_user="wp_$(pwgen -Av 9 1)" 21 | echo "export WP_USER=$wp_user" >> /root/.envrc 22 | fi 23 | 24 | home_basename=$(echo $wp_user | awk -F _ '{print $1}') 25 | 26 | #--- please do not edit below this file ---# 27 | 28 | SSHD_CONFIG='/etc/ssh/sshd_config' 29 | 30 | if [ ! -d "/home/${home_basename}" ]; then 31 | useradd --shell=/bin/bash -m --home-dir /home/${home_basename} $wp_user 32 | 33 | groupadd ${home_basename} 34 | gpasswd -a $wp_user ${home_basename} &> /dev/null 35 | fi 36 | 37 | wp_pass=${WP_PASSWORD:-""} 38 | if [ "$wp_pass" == "" ]; then 39 | wp_pass=$(pwgen -cns 12 1) 40 | echo "export WP_PASSWORD=$wp_pass" >> /root/.envrc 41 | 42 | echo "$wp_user:$wp_pass" | chpasswd 43 | fi 44 | 45 | # cd $local_wp_in_a_box_repo/scripts/ &> /dev/null 46 | # sudo -H -u $wp_user bash nvm-nodejs.sh 47 | # cd - &> /dev/null 48 | 49 | echo ...done creating a WP user. 50 | --------------------------------------------------------------------------------