├── .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 |
--------------------------------------------------------------------------------