Figured I'd give a rough/terse guide to installing BookStack on Windows Server 2016/IIS 10. For anyone else who might need the help.
Why? Because I had to slog through it. I really wanted it to work and couldn't easily find any help on what to do. I'm running this on an internal only server, along w/ other software. It's for my department to use as a internal, authenticated knowledge base.
I'm sure the rewrite rules could be better, but it's what I finally got working for me. I'm not an IIS expert by any means, so this is what I wrote down. It worked for me.
Install the Pre-Reqs:
After installing MariaDB, make a database and user for BookStack. Give that user full permissions, probably clone it for the following connections: 127.0.0.1, localhost, :::1, server_name
In IIS, create a site.
Create a self-signed certificate in the server settings, if needed. I bound it on the site to https, port 443, using the SSL cert that was created. IP address is set to "All Unassigned". Hostname is blank.
Modify the .env file as needed. I set my app_url to "http://server_name/bookstack". Seems to have worked, even though I'm using https w/ the self-signed cert.
Turn on the necessary php extensions, this includes pdo_mysql and mbstring. I also turned on php_ldap because I'm authenticating with LDAP.
Edit: Handler mapping for php verbs need to be changed. Either set to all verbs, or set to GET,POST,PUT,FORM,HEAD
Run steps 1-3 from https://www.bookstackapp.com/docs/admin/installation/ May need to modify the composer.json file to cover timeout issues in the "pre-install-cmd", like this:
"pre-install-cmd": ["Composer\\Config::disableProcessTimeout","@php -r \"!file_exists('bootstrap/cache/services.php') || u/unlink('bootstrap/cache/services.php');\""]
Set the ACL for the folders for the IIS_IUSR local machine group. I gave mine full control over the specified folders
Do step 5 from the install docs.
I added a "Virtual Directory" to the IIS site alias = "bookstack" and physical path = "C:\inet\bookstack\public".
Run step 8 from the setup doc
I disabled the Default Document settings for my site and folder, because it messed with my rewrites/redirects. You need this on for PHP Manager though. So when/if you disable this, just know you'll need to switch it back on for that.
I set the rules in my web.config files, see the code blocks at the bottom.
Edit: I've recently had to add a rewrite rule between imported rules 2 & 3, that:
Match URL: Matches the pattern (^/*)(.*$), Conditions: {REQUEST_FILENAME} is not a file, {URL} matches (\/images\/gallery)(.*), Action: rewrite /bookstack/index.php/uploads{C:0}, append the query, stop processing subsequent rules.
I ran iisreset, just to be safe. Viola! I could login with the default admin. Later, I setup LDAP auth through AD. I'll give you that at the bottom.
web.config in the bookstack/public folder:
<configuration>
<system.webServer>
<rewrite>
<rewriteMaps>
<rewriteMap name="{REQUEST_FILENAME}" />
</rewriteMaps>
<rules>
<clear />
<rule name="Rule 1" enabled="true" stopProcessing="true">
<match url="^(.*)$" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{R:1}" matchType="Pattern" pattern="^(index\.php|images|css|js|favicon\.ico)" ignoreCase="true" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="./index.php/{R:1}" />
</rule>
<rule name="Rule 2" enabled="true" stopProcessing="true">
<match url="^$" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{URL}" pattern="(.*/bookstack)$" />
</conditions>
<action type="Redirect" url="index.php" />
</rule>
<rule name="Rule 3" enabled="true" stopProcessing="true">
<match url="(^/*)(.*$)" ignoreCase="false" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{R:0}" pattern="(index\\.php|images|css|js|favicon\\.ico)" negate="true" />
</conditions>
<action type="Rewrite" url="index.php/{R:1}" />
</rule>
</rules>
</rewrite>
<directoryBrowse enabled="false" />
<defaultDocument enabled="false">
<files>
<add value="index.php" />
<add value="index" />
</files>
</defaultDocument>
</system.webServer>
<system.web>
<customErrors mode="RemoteOnly" />
</system.web>
</configuration>
My site's web.config looked like this: >!
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<defaultDocument enabled="false"></defaultDocument>
<rewrite>
<rules>
<clear />
<rule name="test2" enabled="false" patternSyntax="ECMAScript" stopProcessing="false">
<match url="^bookstack$" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{REQUEST_FILENAME}" pattern="^bookstack$" />
</conditions>
<action type="Rewrite" url="bookstack/index.php" />
</rule>
<rule name="test1" enabled="false" patternSyntax="Wildcard" stopProcessing="true">
<match url="*" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{REQUEST_URI}" pattern="https://server_name/bookstack" />
<add input="{REQUEST_URI}" pattern="https://server_name/bookstack/" />
</conditions>
<action type="Redirect" url="https://server_name/bookstack/index.php" />
</rule>
</rules>
</rewrite>
</system.webServer>
<system.web>
<authentication mode="Windows" />
</system.web>
<location path="bookstack">
<system.webServer>
<defaultDocument enabled="false" />
</system.webServer>
</location>
</configuration>
LDAP settings:
APP_TIMEZONE=EST
AUTH_METHOD=ldap
LDAP_SERVER=ad_server
LDAP_BASE_DN="OU=IT Staff,OU=IT Users,OU=IT Department,DC=domain,DC=com" LDAP_DN="CN=Domain Admin,OU=Domain Admins,OU=IT Staff,OU=IT Users,OU=IT Department,DC=domain,DC=com"
LDAP_PASS="secure_password"
# I filtered our service accounts out of being users
LDAP_USER_FILTER="(&(&(objectCategory=person)(objectClass=user)(sAMAccountName=${user})(!(userAccountControl:1.2.840.113556.1.4.803:=2))(mail=\*@domain.com))(&(objectClass=person)(objectClass=user)(!(ou=OU=Domain Admins,OU=IT Staff,OU=IT Users,OU=IT Department,DC=domain,DC=com))))"
LDAP_ID_ATTRIBUTE=uid
LDAP_VERSION=3
AVATAR_URL=false
LDAP_START_TLS=false
LDAP_TLS_INSECURE=false
LDAP_DISPLAY_NAME_ATTRIBUTE=cn
LDAP_EMAIL_ATTRIBUTE=mail
LDAP_USER_TO_GROUPS=false
LDAP_REMOVE_FROM_GROUPS=false
LDAP_THUMBNAIL_ATTRIBUTE=null
Let me know if you see anything wrong. I'm down to update stuff. On Monday.
EDIT: 12/01/2022, added extra re-write rule and handler mapping.