├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── config ├── apache │ └── bookstack.conf └── nginx │ ├── subpath-proxy-config │ └── ubuntu-1604-install-config ├── meta-scripts ├── bookstack-changelog ├── bookstack-format-crowdin-members-report ├── bookstack-new-release-blogpost ├── bookstack-release-steps └── bookstack-update-translators └── scripts ├── installation-ubuntu-16.04.sh ├── installation-ubuntu-18.04.sh ├── installation-ubuntu-20.04.sh ├── installation-ubuntu-22.04.sh └── installation-ubuntu-24.04.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ssddanbrown] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ 2 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 BookStackApp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BookStack Devops 2 | 3 | This repo contains scripts & configuration used for 4 | installation & project maintenance of [BookStack](https://www.bookstackapp.com/). 5 | 6 | For installation scripts, we focus on supporting Ubuntu LTS releases only to keep maintenance feasible. 7 | 8 | Please don't offer alternative systems or methods of hosting (Such as docker, VMs etc...). 9 | The scope of official support is kept narrow to ensure we can continue to offer some level of support to users 10 | using these scripts and configurations. -------------------------------------------------------------------------------- /config/apache/bookstack.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | # This is a simple example of an Apache VirtualHost configuration 4 | # file that could be used with BookStack. 5 | # This assumes mod_php has been installed and is loaded. 6 | # 7 | # Change the "docs.example.com" usage in the "ServerName" directive 8 | # to be your web domain for BookStack. 9 | # 10 | # Change the "/var/www/bookstack/public/", used twice below, to the 11 | # location of the "public" folder within your BookStack installation. 12 | # 13 | # This configuration is only for HTTP, Not HTTPS. 14 | # For HTTPS we recommend using https://certbot.eff.org/ 15 | 16 | ServerName docs.example.com 17 | DocumentRoot /var/www/bookstack/public/ 18 | 19 | 20 | Options Indexes FollowSymLinks 21 | AllowOverride None 22 | Require all granted 23 | 24 | 25 | Options -MultiViews -Indexes 26 | 27 | 28 | RewriteEngine On 29 | 30 | # Handle Authorization Header 31 | RewriteCond %{HTTP:Authorization} . 32 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 33 | 34 | # Redirect Trailing Slashes If Not A Folder... 35 | RewriteCond %{REQUEST_FILENAME} !-d 36 | RewriteCond %{REQUEST_URI} (.+)/$ 37 | RewriteRule ^ %1 [L,R=301] 38 | 39 | # Handle Front Controller... 40 | RewriteCond %{REQUEST_FILENAME} !-d 41 | RewriteCond %{REQUEST_FILENAME} !-f 42 | RewriteRule ^ index.php [L] 43 | 44 | 45 | 46 | ErrorLog ${APACHE_LOG_DIR}/error.log 47 | CustomLog ${APACHE_LOG_DIR}/access.log combined 48 | 49 | -------------------------------------------------------------------------------- /config/nginx/subpath-proxy-config: -------------------------------------------------------------------------------- 1 | # This example nginx config file shows a method of hosting BookStack on a 2 | # URL sub path (http://example.com/bookstack). There are many ways to achieve 3 | # this but the below method uses a reverse proxy style method to keep the BookStack 4 | # configuration contained within it's own server block. 5 | 6 | # There are two server blocks in this file. 7 | # The first is pointed at BookStack so has the PHP config, but 8 | # this is only intended to be used locally (hence on port 8080 and using "localhost"). 9 | # The "root" directory is pointed to the "public" folder within your BookStack 10 | # install folder. 11 | 12 | # The second would represent your existing website (http://example.com in this case). 13 | # The "location /bookstack/" block will take web requests to `/bookstack/` and proxy 14 | # them to the first internal-only server block. 15 | 16 | # Note: The "root" of the first server block should not be a child directory of the 17 | # second server block. This would likely cause an insecure setup. 18 | 19 | server { 20 | listen 8080; 21 | listen [::]:8080; 22 | 23 | server_name localhost; 24 | 25 | root /var/www/bookstack/public; 26 | index index.php index.html; 27 | 28 | location / { 29 | try_files $uri $uri/ /index.php?$query_string; 30 | } 31 | 32 | location ~ \.php$ { 33 | include snippets/fastcgi-php.conf; 34 | fastcgi_pass unix:/run/php/php7.4-fpm.sock; 35 | } 36 | } 37 | 38 | 39 | server { 40 | listen 80; 41 | listen [::]:80; 42 | 43 | server_name example.com; 44 | 45 | root /var/www/html; 46 | index index.html; 47 | 48 | location /bookstack/ { 49 | proxy_pass http://localhost:8080/; 50 | proxy_redirect off; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /config/nginx/ubuntu-1604-install-config: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | 5 | server_name bookstack.dev; 6 | 7 | root /var/www/bookstack/public; 8 | index index.php index.html; 9 | 10 | location / { 11 | try_files $uri $uri/ /index.php?$query_string; 12 | } 13 | 14 | location ~ \.php$ { 15 | include snippets/fastcgi-php.conf; 16 | fastcgi_pass unix:/run/php/php7.4-fpm.sock; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /meta-scripts/bookstack-changelog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | $url, 17 | CURLOPT_RETURNTRANSFER => 1, 18 | CURLOPT_HTTPHEADER => [ 19 | 'User-Agent: ssddanbrown', 20 | ] 21 | ]); 22 | $data = curl_exec($ch); 23 | 24 | $err = curl_error($ch); 25 | curl_close($ch); 26 | if (!empty($err)) error($err); 27 | 28 | return json_decode($data, true); 29 | } 30 | 31 | // Check a milestone is provided 32 | if (count($argv) < 2) { 33 | error("Milestone ID required"); 34 | } 35 | 36 | // Get milestone info from GitHub 37 | $milestoneID = intval($argv[1]); 38 | 39 | $issues = []; 40 | $requestIssues = []; 41 | $page = 1; 42 | $issueURL = 'https://api.github.com/repos/BookStackApp/BookStack/issues?milestone='. $milestoneID .'&state=all'; 43 | do { 44 | $requestIssues = getJSON($issueURL . '&page=' . $page); 45 | $issues = array_merge($issues, $requestIssues); 46 | $page++; 47 | } while (count($requestIssues) > 0); 48 | 49 | // Get BookStack version and check if a feature or minor release 50 | $milestone = $issues[0]['milestone']; 51 | $versionMatch = []; 52 | preg_match('/v[0-9.]{5,7}/', $milestone['title'], $versionMatch); 53 | $version = $versionMatch[0]; 54 | $splitVersion = explode('.', $version); 55 | $isFeature = count($splitVersion) === 2; 56 | 57 | // Output title 58 | output("# BookStack {$version}\n"); 59 | 60 | // Output header text and links 61 | output("### Links\n"); 62 | output("- [Update instructions](https://www.bookstackapp.com/docs/admin/updates)"); 63 | if ($isFeature) { 64 | $urlVersion = implode('-', $splitVersion); 65 | output("- [Update details on blog](https://www.bookstackapp.com/blog/bookstack-release-{$urlVersion}/)"); 66 | } 67 | 68 | output("\n### Full List of Changes\n"); 69 | output("This release contains the following fixes and changes:\n"); 70 | 71 | // Output issues 72 | foreach ($issues as $issue) { 73 | $output = '* '; 74 | $output .= trim($issue['title'], '.') . '.'; 75 | if (isset($issue['pull_request']) && $issue['user']['login'] !== 'ssddanbrown') { 76 | $output .= " Thanks to [@{$issue['user']['login']}]({$issue['html_url']})."; 77 | } 78 | $output .= " ([#{$issue['number']}]({$issue['html_url']}))"; 79 | output($output); 80 | } -------------------------------------------------------------------------------- /meta-scripts/bookstack-format-crowdin-members-report: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | user->username . '.jpg'; 22 | $imageOutPath = implode(DIRECTORY_SEPARATOR, [$siteDir, "static/images/blog-cover-images", $imageOutName]); 23 | file_put_contents($imageOutPath, $imageContent); 24 | 25 | // Write out blogpost 26 | $hyphenVersion = str_replace('.', '-', trim($version)); 27 | $postMdName = "bookstack-release-{$hyphenVersion}.md"; 28 | $postMdOutPath = implode(DIRECTORY_SEPARATOR, [$siteDir, "content/blog", $postMdName]); 29 | $postMdContent = getPostMarkdown(); 30 | file_put_contents($postMdOutPath, $postMdContent); 31 | 32 | output("Done, Post created at {$postMdOutPath}"); 33 | 34 | function unsplashGet(string $id, string $unsplashKey) { 35 | $json = file_get_contents("https://api.unsplash.com/photos/{$id}?client_id={$unsplashKey}"); 36 | return json_decode($json); 37 | } 38 | 39 | function getImageContent($unsplashImage, int $width, int $height, int $quality) { 40 | $url = $unsplashImage->urls->raw; 41 | $url .= "&fm=jpg&w={$width}&h={$height}&fit=crop&q={$quality}&crop=focalpoint,center"; 42 | return file_get_contents($url); 43 | } 44 | 45 | function getPostMarkdown() { 46 | global $image, $imageContent, $version, $hyphenVersion, $imageOutName; 47 | $date = str_replace('+00:00', 'Z', date('c')); 48 | $content = <<Header Image Credits: Photo by {$image->user->name} on Unsplash 96 | POSTCONTENT; 97 | return $content; 98 | } 99 | 100 | function error($text) { 101 | echo $text . "\n"; 102 | exit(1); 103 | } 104 | 105 | function output($text) { 106 | echo $text . "\n"; 107 | } 108 | 109 | function read($question): string { 110 | $response = readline($question); 111 | if (!$response) { 112 | error("Failed to respond to question (" . $question . ")"); 113 | } 114 | 115 | return $response; 116 | } -------------------------------------------------------------------------------- /meta-scripts/bookstack-release-steps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Enter the full version (eg. v24.03 or v24.12.3) and press [ENTER]:" 4 | read version 5 | hyphen_version="${version//./-}" 6 | blogpost_url="https://www.bookstackapp.com/blog/bookstack-release-${hyphen_version}/" 7 | 8 | echo "" 9 | 10 | echo -e "\e[1m\e[94m== Translator & Dependency Attribution Update ==\e[0m" 11 | echo "bookstack-update-translators" 12 | echo "composer run build-licenses" 13 | echo "git commit -a -m \"Updated translator & dependency attribution before release ${version}\"" 14 | echo "" 15 | 16 | echo -e "\e[1m\e[94m== Merge codebase from development ==\e[0m" 17 | echo "git checkout release" 18 | echo "git merge development" 19 | echo "" 20 | 21 | echo -e "\e[1m\e[94m== Builds deps and increment version ==\e[0m" 22 | echo "npm run production" 23 | echo "echo \"${version}\" > version" 24 | echo "git commit -a -m \"Updated version and assets for release ${version}\"" 25 | echo "" 26 | 27 | echo -e "\e[1m\e[94m== Tag release and push it to GitHub ==\e[0m" 28 | echo "git tag -a ${version} -m \"Release ${version}\" -s" 29 | echo "git push origin release" 30 | echo "git push origin ${version}" 31 | echo "" 32 | 33 | 34 | echo -e "\e[1m\e[94m== Post Deployment Checklist ==\e[0m" 35 | echo "✔ Create GitHub release - https://github.com/BookStackApp/BookStack/releases/new?tag=${version}&title=BookStack+${version}" 36 | echo "✔ Deploy site blogpost/changes" 37 | echo "✔ Post on Twitter - https://twitter.com/share?url=${blogpost_url}" 38 | echo "✔ Post on Mastodon - https://fosstodon.org/share?url=${blogpost_url}" 39 | echo "✔ Post on Subreddit - http://www.reddit.com/r/BookStack/submit?url=${blogpost_url}" 40 | echo "✔ Update demo instance" 41 | echo "" 42 | echo -e "\e[1m\e[93m🔒 Security release?\e[0m" 43 | echo "✔ Send out security email - https://updates.bookstackapp.com/" 44 | echo "✔ Add notice to updates page" -------------------------------------------------------------------------------- /meta-scripts/bookstack-update-translators: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | $languages) { 35 | if (count($languages) === 0 || (count($languages) === 1 && empty($languages[0]))) continue; 36 | if ($name === 'Dan Brown (ssddanbrown)' || $name === 'Name') continue; 37 | $output .= $name . $reportDelimiter . implode('; ', $languages) . "\n"; 38 | } 39 | 40 | file_put_contents($reportLocation, $output); 41 | } 42 | 43 | function mergeCsvDataIntoReportMap(array &$reportMap, array $csvData, string $reportDelimiter) { 44 | foreach ($csvData as $csvLine) { 45 | if (intval($csvLine['Target Words']) == 0) { 46 | continue; 47 | } 48 | $name = $csvLine['Name']; 49 | $name = str_replace($reportDelimiter, '', $name); 50 | $languages = explode('; ', $csvLine['Languages']); 51 | if (isset($reportMap[$name])) { 52 | $languages = array_unique(array_merge($languages, $reportMap[$name])); 53 | } 54 | $reportMap[$name] = $languages; 55 | } 56 | } 57 | 58 | function loadExistingReportIntoMap($reportDelimiter, $reportLocation) { 59 | try { 60 | $reportData = file_get_contents($reportLocation); 61 | } catch (Exception $exception) { 62 | $reportData = ''; 63 | } 64 | $reportLines = explode("\n", $reportData); 65 | $reportMap = []; 66 | foreach ($reportLines as $reportLine) { 67 | if (empty($reportLine)) continue; 68 | [$name, $langs] = explode($reportDelimiter, $reportLine); 69 | $splitLangs = explode('; ', $langs); 70 | $reportMap[$name] = $splitLangs; 71 | } 72 | return $reportMap; 73 | } 74 | 75 | function exportTopMembersReport($key) { 76 | $result = makeMemberExportReport($key); 77 | 78 | $exportHash = $result->data->identifier; 79 | echo "Waiting for Crowdin report to be generated...\n"; 80 | sleep(5); 81 | echo "Downloading Crowdin report...\n"; 82 | $csv = downloadMemberReport($exportHash, $key); 83 | 84 | return $csv; 85 | } 86 | 87 | function makeMemberExportReport(string $key) { 88 | $url = 'https://api.crowdin.com/api/v2/projects/377219/reports'; 89 | $postData = [ 90 | 'name' => 'top-members', 91 | 'schema' => [ 92 | 'dateFrom' => '2019-10-01T00:00:00Z', 93 | 'dateTo' => date('Y-m-d') . 'T23:59:59Z', 94 | 'format' => 'csv', 95 | ], 96 | ]; 97 | 98 | $ch = curl_init(); 99 | curl_setopt($ch, CURLOPT_URL, $url); 100 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 101 | curl_setopt($ch, CURLOPT_TIMEOUT, 15); 102 | curl_setopt($ch, CURLOPT_POST, true); 103 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData)); 104 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ 105 | 'Content-Type: application/json', 106 | 'Authorization: Bearer ' . $key, 107 | ]); 108 | 109 | $result = curl_exec($ch); 110 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 111 | $error = curl_error($ch); 112 | if ($error) { 113 | throw new Exception($error); 114 | } 115 | 116 | curl_close($ch); 117 | 118 | $data = json_decode($result); 119 | 120 | return $data; 121 | } 122 | 123 | function downloadMemberReport(string $exportHash, string $key) { 124 | $params = [ 125 | 'hash' => $exportHash, 126 | 'key' => $key 127 | ]; 128 | $url = "https://api.crowdin.com/api/v2/projects/377219/reports/{$exportHash}/download"; 129 | $ch = curl_init(); 130 | curl_setopt($ch, CURLOPT_URL, $url); 131 | curl_setopt($ch, CURLOPT_TIMEOUT, 15); 132 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 133 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ 134 | 'Content-Type: application/json', 135 | 'Authorization: Bearer ' . $key, 136 | ]); 137 | 138 | $result = curl_exec($ch); 139 | curl_close($ch); 140 | $data = json_decode($result); 141 | 142 | $downloadUrl = $data->data->url ?? null; 143 | if (!$downloadUrl) { 144 | throw new Exception("Could not get report download URL. Download response data:\n" . $result); 145 | } 146 | 147 | return file_get_contents($downloadUrl); 148 | } 149 | 150 | /** 151 | * Convert a comma separated string into an associated array. 152 | * @link http://gist.github.com/385876 (Modified) 153 | * @author Jay Williams (Modified) 154 | * @copyright Copyright (c) 2010, Jay Williams (Modified) 155 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 156 | */ 157 | function csv_to_array(string $csvString): array 158 | { 159 | 160 | $header = null; 161 | $data = []; 162 | $lines = explode("\n", trim($csvString)); 163 | foreach ($lines as $line) { 164 | $csvLine = str_getcsv($line); 165 | if (!$header) { 166 | $header = $csvLine; 167 | } else { 168 | $data[] = array_combine($header, $csvLine); 169 | } 170 | } 171 | 172 | return $data; 173 | } 174 | -------------------------------------------------------------------------------- /scripts/installation-ubuntu-16.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script will install a new BookStack instance on a fresh Ubuntu 16.04 server. 3 | # This script is experimental and does not ensure any security. 4 | 5 | echo "THIS SCRIPT IS NO LONGER SUPPORTED OR MAINTAINED" 6 | echo "IT MAY NOT WORK WITH CURRENT VERSIONS OF BOOKSTACK" 7 | echo "" 8 | 9 | echo "" 10 | echo -n "Enter the domain you want to host BookStack and press [ENTER]: " 11 | read DOMAIN 12 | 13 | myip=$(ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/') 14 | 15 | export DEBIAN_FRONTEND=noninteractive 16 | apt update 17 | apt install -y software-properties-common python-software-properties 18 | add-apt-repository -yu ppa:ondrej/php 19 | apt install -y git nginx curl php7.4 php7.4-fpm php7.4-curl php7.4-mbstring php7.4-ldap \ 20 | php7.4-tidy php7.4-xml php7.4-zip php7.4-gd php7.4-mysql mysql-server-5.7 21 | 22 | # Set up database 23 | DB_PASS="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13)" 24 | mysql -u root --execute="CREATE DATABASE bookstack;" 25 | mysql -u root --execute="CREATE USER 'bookstack'@'localhost' IDENTIFIED BY '$DB_PASS';" 26 | mysql -u root --execute="GRANT ALL ON bookstack.* TO 'bookstack'@'localhost';FLUSH PRIVILEGES;" 27 | 28 | # Download BookStack 29 | cd /var/www 30 | git clone https://github.com/ssddanbrown/BookStack.git --branch release --single-branch bookstack 31 | BOOKSTACK_DIR="/var/www/bookstack" 32 | cd $BOOKSTACK_DIR 33 | 34 | # Install composer 35 | EXPECTED_SIGNATURE=$(wget https://composer.github.io/installer.sig -O - -q) 36 | curl -s https://getcomposer.org/installer > composer-setup.php 37 | ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');") 38 | 39 | if [ "$EXPECTED_SIGNATURE" = "$ACTUAL_SIGNATURE" ] 40 | then 41 | php composer-setup.php --quiet 42 | RESULT=$? 43 | rm composer-setup.php 44 | else 45 | >&2 echo 'ERROR: Invalid composer installer signature' 46 | rm composer-setup.php 47 | exit 1 48 | fi 49 | 50 | # Install BookStack composer dependencies 51 | php composer.phar install --no-dev 52 | 53 | # Copy and update BookStack environment variables 54 | cp .env.example .env 55 | sed -i.bak "s@APP_URL=.*\$@APP_URL=http://$DOMAIN@" .env 56 | sed -i.bak 's/DB_DATABASE=.*$/DB_DATABASE=bookstack/' .env 57 | sed -i.bak 's/DB_USERNAME=.*$/DB_USERNAME=bookstack/' .env 58 | sed -i.bak "s/DB_PASSWORD=.*\$/DB_PASSWORD=$DB_PASS/" .env 59 | 60 | # Generate the application key 61 | php artisan key:generate --no-interaction --force 62 | # Migrate the databases 63 | php artisan migrate --no-interaction --force 64 | 65 | # Set file and folder permissions 66 | chown www-data:www-data -R bootstrap/cache public/uploads storage && chmod -R 755 bootstrap/cache public/uploads storage 67 | 68 | # Add nginx configuration 69 | curl -s https://raw.githubusercontent.com/BookStackApp/devops/main/config/nginx/ubuntu-1604-install-config > /etc/nginx/sites-available/bookstack 70 | sed -i.bak "s/bookstack.dev/$DOMAIN/" /etc/nginx/sites-available/bookstack 71 | ln -s /etc/nginx/sites-available/bookstack /etc/nginx/sites-enabled/bookstack 72 | 73 | # Remove the default nginx configuration 74 | rm /etc/nginx/sites-enabled/default 75 | 76 | # Restart nginx to load new config 77 | service nginx restart 78 | 79 | echo "" 80 | echo "Setup Finished, Your BookStack instance should now be installed." 81 | echo "You can login with the email 'admin@admin.com' and password of 'password'" 82 | echo "MySQL was installed without a root password, It is recommended that you set a root MySQL password." 83 | echo "" 84 | echo "You can access your BookStack instance at: http://$myip/" 85 | -------------------------------------------------------------------------------- /scripts/installation-ubuntu-18.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script will install a new BookStack instance on a fresh Ubuntu 18.04 server. 3 | # This script is experimental and does not ensure any security. 4 | 5 | # Fetch domain to use from first provided parameter, 6 | # Otherwise request the user to input their domain 7 | DOMAIN=$1 8 | if [ -z $1 ] 9 | then 10 | echo "" 11 | printf "Enter the domain you want to host BookStack and press [ENTER]\nExamples: my-site.com or docs.my-site.com\n" 12 | read DOMAIN 13 | fi 14 | 15 | # Get the current machine IP address 16 | CURRENT_IP=$(ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/') 17 | 18 | # Install core system packages 19 | apt update 20 | apt install -y software-properties-common 21 | export DEBIAN_FRONTEND=noninteractive 22 | add-apt-repository universe 23 | add-apt-repository -yu ppa:ondrej/php 24 | apt install -y git apache2 curl php8.2 php8.2-curl php8.2-mbstring php8.2-ldap \ 25 | php8.2-xml php8.2-zip php8.2-gd php8.2-mysql mysql-server-5.7 libapache2-mod-php8.2 26 | 27 | # Set up database 28 | DB_PASS="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13)" 29 | mysql -u root --execute="CREATE DATABASE bookstack;" 30 | mysql -u root --execute="CREATE USER 'bookstack'@'localhost' IDENTIFIED BY '$DB_PASS';" 31 | mysql -u root --execute="GRANT ALL ON bookstack.* TO 'bookstack'@'localhost';FLUSH PRIVILEGES;" 32 | 33 | # Download BookStack 34 | cd /var/www 35 | git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch bookstack 36 | BOOKSTACK_DIR="/var/www/bookstack" 37 | cd $BOOKSTACK_DIR 38 | 39 | # Install composer 40 | EXPECTED_SIGNATURE=$(wget https://composer.github.io/installer.sig -O - -q) 41 | curl -s https://getcomposer.org/installer > composer-setup.php 42 | ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');") 43 | 44 | if [ "$EXPECTED_SIGNATURE" = "$ACTUAL_SIGNATURE" ] 45 | then 46 | php composer-setup.php --quiet 47 | RESULT=$? 48 | rm composer-setup.php 49 | else 50 | >&2 echo 'ERROR: Invalid composer installer signature' 51 | rm composer-setup.php 52 | exit 1 53 | fi 54 | 55 | # Install BookStack composer dependencies 56 | export COMPOSER_ALLOW_SUPERUSER=1 57 | php composer.phar install --no-dev --no-plugins 58 | 59 | # Copy and update BookStack environment variables 60 | cp .env.example .env 61 | sed -i.bak "s@APP_URL=.*\$@APP_URL=http://$DOMAIN@" .env 62 | sed -i.bak 's/DB_DATABASE=.*$/DB_DATABASE=bookstack/' .env 63 | sed -i.bak 's/DB_USERNAME=.*$/DB_USERNAME=bookstack/' .env 64 | sed -i.bak "s/DB_PASSWORD=.*\$/DB_PASSWORD=$DB_PASS/" .env 65 | 66 | # Generate the application key 67 | php artisan key:generate --no-interaction --force 68 | # Migrate the databases 69 | php artisan migrate --no-interaction --force 70 | 71 | # Set file and folder permissions 72 | chown www-data:www-data -R bootstrap/cache public/uploads storage && chmod -R 755 bootstrap/cache public/uploads storage 73 | 74 | # Set up apache 75 | a2enmod rewrite 76 | a2enmod php8.2 77 | 78 | cat >/etc/apache2/sites-available/bookstack.conf < 80 | ServerName ${DOMAIN} 81 | 82 | ServerAdmin webmaster@localhost 83 | DocumentRoot /var/www/bookstack/public/ 84 | 85 | 86 | Options Indexes FollowSymLinks 87 | AllowOverride None 88 | Require all granted 89 | 90 | 91 | Options -MultiViews -Indexes 92 | 93 | 94 | RewriteEngine On 95 | 96 | # Handle Authorization Header 97 | RewriteCond %{HTTP:Authorization} . 98 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 99 | 100 | # Redirect Trailing Slashes If Not A Folder... 101 | RewriteCond %{REQUEST_FILENAME} !-d 102 | RewriteCond %{REQUEST_URI} (.+)/$ 103 | RewriteRule ^ %1 [L,R=301] 104 | 105 | # Handle Front Controller... 106 | RewriteCond %{REQUEST_FILENAME} !-d 107 | RewriteCond %{REQUEST_FILENAME} !-f 108 | RewriteRule ^ index.php [L] 109 | 110 | 111 | 112 | ErrorLog \${APACHE_LOG_DIR}/error.log 113 | CustomLog \${APACHE_LOG_DIR}/access.log combined 114 | 115 | 116 | EOL 117 | 118 | a2dissite 000-default.conf 119 | a2ensite bookstack.conf 120 | 121 | # Restart apache to load new config 122 | systemctl restart apache2 123 | 124 | echo "" 125 | echo "Setup Finished, Your BookStack instance should now be installed." 126 | echo "You can login with the email 'admin@admin.com' and password of 'password'" 127 | echo "MySQL was installed without a root password, It is recommended that you set a root MySQL password." 128 | echo "" 129 | echo "You can access your BookStack instance at: http://$CURRENT_IP/ or http://$DOMAIN/" 130 | -------------------------------------------------------------------------------- /scripts/installation-ubuntu-20.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script will install a new BookStack instance on a fresh Ubuntu 20.04 server. 3 | # This script is experimental and does not ensure any security. 4 | 5 | # Fetch domain to use from first provided parameter, 6 | # Otherwise request the user to input their domain 7 | DOMAIN=$1 8 | if [ -z "$1" ] 9 | then 10 | echo "" 11 | printf "Enter the domain you want to host BookStack and press [ENTER]\nExamples: my-site.com or docs.my-site.com\n" 12 | read -r DOMAIN 13 | fi 14 | 15 | # Ensure a domain was provided otherwise display 16 | # an error message and stop the script 17 | if [ -z "$DOMAIN" ] 18 | then 19 | >&2 echo 'ERROR: A domain must be provided to run this script' 20 | exit 1 21 | fi 22 | 23 | # Get the current machine IP address 24 | CURRENT_IP=$(ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/') 25 | 26 | # Install core system packages 27 | apt update 28 | apt install -y software-properties-common 29 | export DEBIAN_FRONTEND=noninteractive 30 | add-apt-repository universe 31 | add-apt-repository -yu ppa:ondrej/php 32 | apt install -y git unzip apache2 curl php8.2 php8.2-curl php8.2-mbstring php8.2-ldap \ 33 | php8.2-xml php8.2-zip php8.2-gd php8.2-mysql mysql-server-8.0 libapache2-mod-php8.2 34 | 35 | # Set up database 36 | DB_PASS="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13)" 37 | mysql -u root --execute="CREATE DATABASE bookstack;" 38 | mysql -u root --execute="CREATE USER 'bookstack'@'localhost' IDENTIFIED WITH mysql_native_password BY '$DB_PASS';" 39 | mysql -u root --execute="GRANT ALL ON bookstack.* TO 'bookstack'@'localhost';FLUSH PRIVILEGES;" 40 | 41 | # Download BookStack 42 | cd /var/www || exit 43 | git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch bookstack 44 | BOOKSTACK_DIR="/var/www/bookstack" 45 | cd $BOOKSTACK_DIR || exit 46 | 47 | # Install composer 48 | EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" 49 | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" 50 | ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" 51 | 52 | if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] 53 | then 54 | >&2 echo 'ERROR: Invalid composer installer checksum' 55 | rm composer-setup.php 56 | exit 1 57 | fi 58 | 59 | php composer-setup.php --quiet 60 | rm composer-setup.php 61 | 62 | # Move composer to global installation 63 | mv composer.phar /usr/local/bin/composer 64 | 65 | # Install BookStack composer dependencies 66 | export COMPOSER_ALLOW_SUPERUSER=1 67 | php /usr/local/bin/composer install --no-dev --no-plugins 68 | 69 | # Copy and update BookStack environment variables 70 | cp .env.example .env 71 | sed -i.bak "s@APP_URL=.*\$@APP_URL=http://$DOMAIN@" .env 72 | sed -i.bak 's/DB_DATABASE=.*$/DB_DATABASE=bookstack/' .env 73 | sed -i.bak 's/DB_USERNAME=.*$/DB_USERNAME=bookstack/' .env 74 | sed -i.bak "s/DB_PASSWORD=.*\$/DB_PASSWORD=$DB_PASS/" .env 75 | 76 | # Generate the application key 77 | php artisan key:generate --no-interaction --force 78 | # Migrate the databases 79 | php artisan migrate --no-interaction --force 80 | 81 | # Set file and folder permissions 82 | chown www-data:www-data -R bootstrap/cache public/uploads storage && chmod -R 755 bootstrap/cache public/uploads storage 83 | 84 | # Set up apache 85 | a2enmod rewrite 86 | a2enmod php8.2 87 | 88 | cat >/etc/apache2/sites-available/bookstack.conf < 90 | ServerName ${DOMAIN} 91 | 92 | ServerAdmin webmaster@localhost 93 | DocumentRoot /var/www/bookstack/public/ 94 | 95 | 96 | Options Indexes FollowSymLinks 97 | AllowOverride None 98 | Require all granted 99 | 100 | 101 | Options -MultiViews -Indexes 102 | 103 | 104 | RewriteEngine On 105 | 106 | # Handle Authorization Header 107 | RewriteCond %{HTTP:Authorization} . 108 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 109 | 110 | # Redirect Trailing Slashes If Not A Folder... 111 | RewriteCond %{REQUEST_FILENAME} !-d 112 | RewriteCond %{REQUEST_URI} (.+)/$ 113 | RewriteRule ^ %1 [L,R=301] 114 | 115 | # Handle Front Controller... 116 | RewriteCond %{REQUEST_FILENAME} !-d 117 | RewriteCond %{REQUEST_FILENAME} !-f 118 | RewriteRule ^ index.php [L] 119 | 120 | 121 | 122 | ErrorLog \${APACHE_LOG_DIR}/error.log 123 | CustomLog \${APACHE_LOG_DIR}/access.log combined 124 | 125 | 126 | EOL 127 | 128 | a2dissite 000-default.conf 129 | a2ensite bookstack.conf 130 | 131 | # Restart apache to load new config 132 | systemctl restart apache2 133 | 134 | echo "" 135 | echo "Setup Finished, Your BookStack instance should now be installed." 136 | echo "You can login with the email 'admin@admin.com' and password of 'password'" 137 | echo "MySQL was installed without a root password, It is recommended that you set a root MySQL password." 138 | echo "" 139 | echo "You can access your BookStack instance at: http://$CURRENT_IP/ or http://$DOMAIN/" 140 | -------------------------------------------------------------------------------- /scripts/installation-ubuntu-22.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "This script installs a new BookStack instance on a fresh Ubuntu 22.04 server." 4 | echo "This script does not ensure system security." 5 | echo "" 6 | 7 | # Generate a path for a log file to output into for debugging 8 | LOGPATH=$(realpath "bookstack_install_$(date +%s).log") 9 | 10 | # Get the current user running the script 11 | SCRIPT_USER="${SUDO_USER:-$USER}" 12 | 13 | # Get the current machine IP address 14 | CURRENT_IP=$(ip addr | grep 'state UP' -A4 | grep 'inet ' | awk '{print $2}' | cut -f1 -d'/') 15 | 16 | # Generate a password for the database 17 | DB_PASS="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13)" 18 | 19 | # The directory to install BookStack into 20 | BOOKSTACK_DIR="/var/www/bookstack" 21 | 22 | # Get the domain from the arguments (Requested later if not set) 23 | DOMAIN=$1 24 | 25 | # Prevent interactive prompts in applications 26 | export DEBIAN_FRONTEND=noninteractive 27 | 28 | # Echo out an error message to the command line and exit the program 29 | # Also logs the message to the log file 30 | function error_out() { 31 | echo "ERROR: $1" | tee -a "$LOGPATH" 1>&2 32 | exit 1 33 | } 34 | 35 | # Echo out an information message to both the command line and log file 36 | function info_msg() { 37 | echo "$1" | tee -a "$LOGPATH" 38 | } 39 | 40 | # Run some checks before installation to help prevent messing up an existing 41 | # web-server setup. 42 | function run_pre_install_checks() { 43 | # Check we're running as root and exit if not 44 | if [[ $EUID -gt 0 ]] 45 | then 46 | error_out "This script must be ran with root/sudo privileges" 47 | fi 48 | 49 | # Check if Apache appears to be installed and exit if so 50 | if [ -d "/etc/apache2/sites-enabled" ] 51 | then 52 | error_out "This script is intended for a fresh server install, existing apache config found, aborting install" 53 | fi 54 | 55 | # Check if MySQL appears to be installed and exit if so 56 | if [ -d "/var/lib/mysql" ] 57 | then 58 | error_out "This script is intended for a fresh server install, existing MySQL data found, aborting install" 59 | fi 60 | } 61 | 62 | # Fetch domain to use from first provided parameter, 63 | # Otherwise request the user to input their domain 64 | function run_prompt_for_domain_if_required() { 65 | if [ -z "$DOMAIN" ] 66 | then 67 | info_msg "" 68 | info_msg "Enter the domain (or IP if not using a domain) you want to host BookStack on and press [ENTER]." 69 | info_msg "Examples: my-site.com or docs.my-site.com or ${CURRENT_IP}" 70 | read -r DOMAIN 71 | fi 72 | 73 | # Error out if no domain was provided 74 | if [ -z "$DOMAIN" ] 75 | then 76 | error_out "A domain must be provided to run this script" 77 | fi 78 | } 79 | 80 | # Install core system packages 81 | function run_package_installs() { 82 | apt update 83 | apt install -y git unzip apache2 php8.1 curl php8.1-curl php8.1-mbstring php8.1-ldap \ 84 | php8.1-xml php8.1-zip php8.1-gd php8.1-mysql mysql-server-8.0 libapache2-mod-php8.1 85 | } 86 | 87 | # Set up database 88 | function run_database_setup() { 89 | mysql -u root --execute="CREATE DATABASE bookstack;" 90 | mysql -u root --execute="CREATE USER 'bookstack'@'localhost' IDENTIFIED WITH mysql_native_password BY '$DB_PASS';" 91 | mysql -u root --execute="GRANT ALL ON bookstack.* TO 'bookstack'@'localhost';FLUSH PRIVILEGES;" 92 | } 93 | 94 | # Download BookStack 95 | function run_bookstack_download() { 96 | cd /var/www || exit 97 | git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch bookstack 98 | } 99 | 100 | # Install composer 101 | function run_install_composer() { 102 | EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" 103 | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" 104 | ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" 105 | 106 | if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] 107 | then 108 | >&2 echo 'ERROR: Invalid composer installer checksum' 109 | rm composer-setup.php 110 | exit 1 111 | fi 112 | 113 | php composer-setup.php --quiet 114 | rm composer-setup.php 115 | 116 | # Move composer to global installation 117 | mv composer.phar /usr/local/bin/composer 118 | } 119 | 120 | # Install BookStack composer dependencies 121 | function run_install_bookstack_composer_deps() { 122 | cd "$BOOKSTACK_DIR" || exit 123 | export COMPOSER_ALLOW_SUPERUSER=1 124 | php /usr/local/bin/composer install --no-dev --no-plugins 125 | } 126 | 127 | # Copy and update BookStack environment variables 128 | function run_update_bookstack_env() { 129 | cd "$BOOKSTACK_DIR" || exit 130 | cp .env.example .env 131 | sed -i.bak "s@APP_URL=.*\$@APP_URL=http://$DOMAIN@" .env 132 | sed -i.bak 's/DB_DATABASE=.*$/DB_DATABASE=bookstack/' .env 133 | sed -i.bak 's/DB_USERNAME=.*$/DB_USERNAME=bookstack/' .env 134 | sed -i.bak "s/DB_PASSWORD=.*\$/DB_PASSWORD=$DB_PASS/" .env 135 | # Generate the application key 136 | php artisan key:generate --no-interaction --force 137 | } 138 | 139 | # Run the BookStack database migrations for the first time 140 | function run_bookstack_database_migrations() { 141 | cd "$BOOKSTACK_DIR" || exit 142 | php artisan migrate --no-interaction --force 143 | } 144 | 145 | # Set file and folder permissions 146 | # Sets current user as owner user and www-data as owner group then 147 | # provides group write access only to required directories. 148 | # Hides the `.env` file so it's not visible to other users on the system. 149 | function run_set_application_file_permissions() { 150 | cd "$BOOKSTACK_DIR" || exit 151 | chown -R "$SCRIPT_USER":www-data ./ 152 | chmod -R 755 ./ 153 | chmod -R 775 bootstrap/cache public/uploads storage 154 | chmod 740 .env 155 | 156 | # Tell git to ignore permission changes 157 | git config core.fileMode false 158 | } 159 | 160 | # Setup apache with the needed modules and config 161 | function run_configure_apache() { 162 | # Enable required apache modules 163 | a2enmod rewrite 164 | a2enmod php8.1 165 | 166 | # Set-up the required BookStack apache config 167 | cat >/etc/apache2/sites-available/bookstack.conf < 169 | ServerName ${DOMAIN} 170 | 171 | ServerAdmin webmaster@localhost 172 | DocumentRoot /var/www/bookstack/public/ 173 | 174 | 175 | Options -Indexes +FollowSymLinks 176 | AllowOverride None 177 | Require all granted 178 | 179 | 180 | Options -MultiViews -Indexes 181 | 182 | 183 | RewriteEngine On 184 | 185 | # Handle Authorization Header 186 | RewriteCond %{HTTP:Authorization} . 187 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 188 | 189 | # Redirect Trailing Slashes If Not A Folder... 190 | RewriteCond %{REQUEST_FILENAME} !-d 191 | RewriteCond %{REQUEST_URI} (.+)/$ 192 | RewriteRule ^ %1 [L,R=301] 193 | 194 | # Handle Front Controller... 195 | RewriteCond %{REQUEST_FILENAME} !-d 196 | RewriteCond %{REQUEST_FILENAME} !-f 197 | RewriteRule ^ index.php [L] 198 | 199 | 200 | 201 | ErrorLog \${APACHE_LOG_DIR}/error.log 202 | CustomLog \${APACHE_LOG_DIR}/access.log combined 203 | 204 | 205 | EOL 206 | 207 | # Disable the default apache site and enable BookStack 208 | a2dissite 000-default.conf 209 | a2ensite bookstack.conf 210 | 211 | # Restart apache to load new config 212 | systemctl restart apache2 213 | } 214 | 215 | info_msg "This script logs full output to $LOGPATH which may help upon issues." 216 | sleep 1 217 | 218 | run_pre_install_checks 219 | run_prompt_for_domain_if_required 220 | info_msg "" 221 | info_msg "Installing using the domain or IP \"$DOMAIN\"" 222 | info_msg "" 223 | sleep 1 224 | 225 | info_msg "[1/9] Installing required system packages... (This may take several minutes)" 226 | run_package_installs >> "$LOGPATH" 2>&1 227 | 228 | info_msg "[2/9] Preparing MySQL database..." 229 | run_database_setup >> "$LOGPATH" 2>&1 230 | 231 | info_msg "[3/9] Downloading BookStack to ${BOOKSTACK_DIR}..." 232 | run_bookstack_download >> "$LOGPATH" 2>&1 233 | 234 | info_msg "[4/9] Installing Composer (PHP dependency manager)..." 235 | run_install_composer >> "$LOGPATH" 2>&1 236 | 237 | info_msg "[5/9] Installing PHP dependencies using composer..." 238 | run_install_bookstack_composer_deps >> "$LOGPATH" 2>&1 239 | 240 | info_msg "[6/9] Creating and populating BookStack .env file..." 241 | run_update_bookstack_env >> "$LOGPATH" 2>&1 242 | 243 | info_msg "[7/9] Running initial BookStack database migrations..." 244 | run_bookstack_database_migrations >> "$LOGPATH" 2>&1 245 | 246 | info_msg "[8/9] Setting BookStack file & folder permissions..." 247 | run_set_application_file_permissions >> "$LOGPATH" 2>&1 248 | 249 | info_msg "[9/9] Configuring apache server..." 250 | run_configure_apache >> "$LOGPATH" 2>&1 251 | 252 | info_msg "----------------------------------------------------------------" 253 | info_msg "Setup finished, your BookStack instance should now be installed!" 254 | info_msg "- Default login email: admin@admin.com" 255 | info_msg "- Default login password: password" 256 | info_msg "- Access URL: http://$CURRENT_IP/ or http://$DOMAIN/" 257 | info_msg "- BookStack install path: $BOOKSTACK_DIR" 258 | info_msg "- Install script log: $LOGPATH" 259 | info_msg "---------------------------------------------------------------" 260 | -------------------------------------------------------------------------------- /scripts/installation-ubuntu-24.04.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "This script installs a new BookStack instance on a fresh Ubuntu 24.04 server." 4 | echo "This script does not ensure system security." 5 | echo "" 6 | 7 | # Generate a path for a log file to output into for debugging 8 | LOGPATH=$(realpath "bookstack_install_$(date +%s).log") 9 | 10 | # Get the current user running the script 11 | SCRIPT_USER="${SUDO_USER:-$USER}" 12 | 13 | # Get the current machine IP address 14 | CURRENT_IP=$(ip addr | grep 'state UP' -A4 | grep 'inet ' | awk '{print $2}' | cut -f1 -d'/') 15 | 16 | # Generate a password for the database 17 | DB_PASS="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13)" 18 | 19 | # The directory to install BookStack into 20 | BOOKSTACK_DIR="/var/www/bookstack" 21 | 22 | # Get the domain from the arguments (Requested later if not set) 23 | DOMAIN=$1 24 | 25 | # Prevent interactive prompts in applications 26 | export DEBIAN_FRONTEND=noninteractive 27 | 28 | # Echo out an error message to the command line and exit the program 29 | # Also logs the message to the log file 30 | function error_out() { 31 | echo "ERROR: $1" | tee -a "$LOGPATH" 1>&2 32 | exit 1 33 | } 34 | 35 | # Echo out an information message to both the command line and log file 36 | function info_msg() { 37 | echo "$1" | tee -a "$LOGPATH" 38 | } 39 | 40 | # Run some checks before installation to help prevent messing up an existing 41 | # web-server setup. 42 | function run_pre_install_checks() { 43 | # Check we're running as root and exit if not 44 | if [[ $EUID -gt 0 ]] 45 | then 46 | error_out "This script must be ran with root/sudo privileges" 47 | fi 48 | 49 | # Check if Apache appears to be installed and exit if so 50 | if [ -d "/etc/apache2/sites-enabled" ] 51 | then 52 | error_out "This script is intended for a fresh server install, existing apache config found, aborting install" 53 | fi 54 | 55 | # Check if MySQL appears to be installed and exit if so 56 | if [ -d "/var/lib/mysql" ] 57 | then 58 | error_out "This script is intended for a fresh server install, existing MySQL data found, aborting install" 59 | fi 60 | } 61 | 62 | # Fetch domain to use from first provided parameter, 63 | # Otherwise request the user to input their domain 64 | function run_prompt_for_domain_if_required() { 65 | if [ -z "$DOMAIN" ] 66 | then 67 | info_msg "" 68 | info_msg "Enter the domain (or IP if not using a domain) you want to host BookStack on and press [ENTER]." 69 | info_msg "Examples: my-site.com or docs.my-site.com or ${CURRENT_IP}" 70 | read -r DOMAIN 71 | fi 72 | 73 | # Error out if no domain was provided 74 | if [ -z "$DOMAIN" ] 75 | then 76 | error_out "A domain must be provided to run this script" 77 | fi 78 | } 79 | 80 | # Install core system packages 81 | function run_package_installs() { 82 | apt update 83 | apt install -y git unzip apache2 curl mysql-server-8.0 php8.3 \ 84 | php8.3-fpm php8.3-curl php8.3-mbstring php8.3-ldap php8.3-xml php8.3-zip php8.3-gd php8.3-mysql 85 | } 86 | 87 | # Set up database 88 | function run_database_setup() { 89 | # Ensure database service has started 90 | systemctl start mysql-server.service 91 | sleep 3 92 | 93 | # Create the required user database, user and permissions in the database 94 | mysql -u root --execute="CREATE DATABASE bookstack;" 95 | mysql -u root --execute="CREATE USER 'bookstack'@'localhost' IDENTIFIED WITH mysql_native_password BY '$DB_PASS';" 96 | mysql -u root --execute="GRANT ALL ON bookstack.* TO 'bookstack'@'localhost';FLUSH PRIVILEGES;" 97 | } 98 | 99 | # Download BookStack 100 | function run_bookstack_download() { 101 | cd /var/www || exit 102 | git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch bookstack 103 | } 104 | 105 | # Install composer 106 | function run_install_composer() { 107 | EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" 108 | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" 109 | ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" 110 | 111 | if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] 112 | then 113 | >&2 echo 'ERROR: Invalid composer installer checksum' 114 | rm composer-setup.php 115 | exit 1 116 | fi 117 | 118 | php composer-setup.php --quiet 119 | rm composer-setup.php 120 | 121 | # Move composer to global installation 122 | mv composer.phar /usr/local/bin/composer 123 | } 124 | 125 | # Install BookStack composer dependencies 126 | function run_install_bookstack_composer_deps() { 127 | cd "$BOOKSTACK_DIR" || exit 128 | export COMPOSER_ALLOW_SUPERUSER=1 129 | php /usr/local/bin/composer install --no-dev --no-plugins 130 | } 131 | 132 | # Copy and update BookStack environment variables 133 | function run_update_bookstack_env() { 134 | cd "$BOOKSTACK_DIR" || exit 135 | cp .env.example .env 136 | sed -i.bak "s@APP_URL=.*\$@APP_URL=http://$DOMAIN@" .env 137 | sed -i.bak 's/DB_DATABASE=.*$/DB_DATABASE=bookstack/' .env 138 | sed -i.bak 's/DB_USERNAME=.*$/DB_USERNAME=bookstack/' .env 139 | sed -i.bak "s/DB_PASSWORD=.*\$/DB_PASSWORD=$DB_PASS/" .env 140 | # Generate the application key 141 | php artisan key:generate --no-interaction --force 142 | } 143 | 144 | # Run the BookStack database migrations for the first time 145 | function run_bookstack_database_migrations() { 146 | cd "$BOOKSTACK_DIR" || exit 147 | php artisan migrate --no-interaction --force 148 | } 149 | 150 | # Set file and folder permissions 151 | # Sets current user as owner user and www-data as owner group then 152 | # provides group write access only to required directories. 153 | # Hides the `.env` file so it's not visible to other users on the system. 154 | function run_set_application_file_permissions() { 155 | cd "$BOOKSTACK_DIR" || exit 156 | chown -R "$SCRIPT_USER":www-data ./ 157 | chmod -R 755 ./ 158 | chmod -R 775 bootstrap/cache public/uploads storage 159 | chmod 740 .env 160 | 161 | # Tell git to ignore permission changes 162 | git config core.fileMode false 163 | } 164 | 165 | # Setup apache with the needed modules and config 166 | function run_configure_apache() { 167 | # Enable required apache modules and config 168 | a2enmod rewrite proxy_fcgi setenvif 169 | a2enconf php8.3-fpm 170 | 171 | # Set-up the required BookStack apache config 172 | cat >/etc/apache2/sites-available/bookstack.conf < 174 | ServerName ${DOMAIN} 175 | 176 | ServerAdmin webmaster@localhost 177 | DocumentRoot /var/www/bookstack/public/ 178 | 179 | 180 | Options -Indexes +FollowSymLinks 181 | AllowOverride None 182 | Require all granted 183 | 184 | 185 | Options -MultiViews -Indexes 186 | 187 | 188 | RewriteEngine On 189 | 190 | # Handle Authorization Header 191 | RewriteCond %{HTTP:Authorization} . 192 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 193 | 194 | # Redirect Trailing Slashes If Not A Folder... 195 | RewriteCond %{REQUEST_FILENAME} !-d 196 | RewriteCond %{REQUEST_URI} (.+)/$ 197 | RewriteRule ^ %1 [L,R=301] 198 | 199 | # Handle Front Controller... 200 | RewriteCond %{REQUEST_FILENAME} !-d 201 | RewriteCond %{REQUEST_FILENAME} !-f 202 | RewriteRule ^ index.php [L] 203 | 204 | 205 | 206 | ErrorLog \${APACHE_LOG_DIR}/error.log 207 | CustomLog \${APACHE_LOG_DIR}/access.log combined 208 | 209 | 210 | EOL 211 | 212 | # Disable the default apache site and enable BookStack 213 | a2dissite 000-default.conf 214 | a2ensite bookstack.conf 215 | 216 | # Restart apache to load new config 217 | systemctl restart apache2 218 | # Ensure php-fpm service has started 219 | systemctl start php8.3-fpm.service 220 | } 221 | 222 | info_msg "This script logs full output to $LOGPATH which may help upon issues." 223 | sleep 1 224 | 225 | run_pre_install_checks 226 | run_prompt_for_domain_if_required 227 | info_msg "" 228 | info_msg "Installing using the domain or IP \"$DOMAIN\"" 229 | info_msg "" 230 | sleep 1 231 | 232 | info_msg "[1/9] Installing required system packages... (This may take several minutes)" 233 | run_package_installs >> "$LOGPATH" 2>&1 234 | 235 | info_msg "[2/9] Preparing MySQL database..." 236 | run_database_setup >> "$LOGPATH" 2>&1 237 | 238 | info_msg "[3/9] Downloading BookStack to ${BOOKSTACK_DIR}..." 239 | run_bookstack_download >> "$LOGPATH" 2>&1 240 | 241 | info_msg "[4/9] Installing Composer (PHP dependency manager)..." 242 | run_install_composer >> "$LOGPATH" 2>&1 243 | 244 | info_msg "[5/9] Installing PHP dependencies using composer..." 245 | run_install_bookstack_composer_deps >> "$LOGPATH" 2>&1 246 | 247 | info_msg "[6/9] Creating and populating BookStack .env file..." 248 | run_update_bookstack_env >> "$LOGPATH" 2>&1 249 | 250 | info_msg "[7/9] Running initial BookStack database migrations..." 251 | run_bookstack_database_migrations >> "$LOGPATH" 2>&1 252 | 253 | info_msg "[8/9] Setting BookStack file & folder permissions..." 254 | run_set_application_file_permissions >> "$LOGPATH" 2>&1 255 | 256 | info_msg "[9/9] Configuring apache server..." 257 | run_configure_apache >> "$LOGPATH" 2>&1 258 | 259 | info_msg "----------------------------------------------------------------" 260 | info_msg "Setup finished, your BookStack instance should now be installed!" 261 | info_msg "- Default login email: admin@admin.com" 262 | info_msg "- Default login password: password" 263 | info_msg "- Access URL: http://$CURRENT_IP/ or http://$DOMAIN/" 264 | info_msg "- BookStack install path: $BOOKSTACK_DIR" 265 | info_msg "- Install script log: $LOGPATH" 266 | info_msg "---------------------------------------------------------------" 267 | --------------------------------------------------------------------------------