diff --git a/.env.example.docker-development b/.env.example.docker-development new file mode 100644 index 000000000..0f5f193db --- /dev/null +++ b/.env.example.docker-development @@ -0,0 +1,31 @@ +# Application key +# Used for encryption where needed. +# Run `php artisan key:generate` to generate a valid key. +APP_KEY=SomeRandomString +DEV_PORT=8080 +APP_ENV=development +APP_DEBUG=true + +# Application URL +# Remove the hash below and set a URL if using BookStack behind +# a proxy, if using a third-party authentication option. +# This must be the root URL that you want to host BookStack on. +# All URL's in BookStack will be generated using this value. +#APP_URL=https://example.com + +# Database details +DB_CONNECTION=mysql_docker_dev + +# Mail system to use +# Can be 'smtp', 'mail' or 'sendmail' +MAIL_DRIVER=smtp + +# SMTP mail options +MAIL_HOST=localhost +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null + + +# A full list of options can be found in the '.env.example.complete' file. \ No newline at end of file diff --git a/app/Config/database.php b/app/Config/database.php index 93a44854f..98063942d 100644 --- a/app/Config/database.php +++ b/app/Config/database.php @@ -92,6 +92,18 @@ return [ 'strict' => false, ], + 'mysql_docker_dev' => [ + 'driver' => 'mysql', + 'host' => 'db', + 'database' => 'bookstack-test', + 'username' => 'bookstack-test', + 'password' => 'bookstack-test', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => false, + ], + 'pgsql' => [ 'driver' => 'pgsql', 'host' => env('DB_HOST', 'localhost'), diff --git a/database/seeds/DummyContentSeeder.php b/database/seeds/DummyContentSeeder.php index ce3cd1307..52386691e 100644 --- a/database/seeds/DummyContentSeeder.php +++ b/database/seeds/DummyContentSeeder.php @@ -11,6 +11,17 @@ class DummyContentSeeder extends Seeder */ public function run() { + // Create admin user + $adminUser = new \BookStack\Auth\User(); + $adminUser->name = 'Admin User'; + $adminUser->email = 'admin@test.local'; + $adminUser->password = Hash::make('admin'); + $adminUser->email_confirmed = true; + $adminUser->setRememberToken(str_random(10)); + $adminUser->save(); + $adminRole = \BookStack\Auth\Role::getRole('admin'); + $adminUser->attachRole($adminRole); + // Create an editor user $editorUser = factory(\BookStack\Auth\User::class)->create(); $editorRole = \BookStack\Auth\Role::getRole('editor'); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..63aa2b0c7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +# This is a Docker Compose configuration +# intended for development purposes + +version: '3' + +volumes: + db: {} + +services: + db: + image: mysql:8 + environment: + MYSQL_DATABASE: bookstack-test + MYSQL_USER: bookstack-test + MYSQL_PASSWORD: bookstack-test + MYSQL_RANDOM_ROOT_PASSWORD: 'true' + command: --default-authentication-plugin=mysql_native_password + volumes: + - db:/var/lib/mysql + app: + build: + context: . + dockerfile: docker/dev/Dockerfile + ports: + - ${DEV_PORT}:80 + volumes: + - ./:/app + entrypoint: /app/docker/dev/entrypoint.app.sh + node: + image: node:alpine + working_dir: /app + volumes: + - ./:/app + entrypoint: /app/docker/dev/entrypoint.node.sh diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile new file mode 100644 index 000000000..5830a5e80 --- /dev/null +++ b/docker/dev/Dockerfile @@ -0,0 +1,15 @@ +FROM php:7.3-apache + +ENV APACHE_DOCUMENT_ROOT /app/public +WORKDIR /app + +RUN apt-get update -y \ + && apt-get install -y libtidy-dev libpng-dev libxml++2.6-dev wait-for-it \ + && docker-php-ext-install pdo pdo_mysql tidy dom xml mbstring gd \ + && a2enmod rewrite \ + && sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \ + && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \ + && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ + && php composer-setup.php \ + && mv composer.phar /usr/bin/composer \ + && php -r "unlink('composer-setup.php');" diff --git a/docker/dev/entrypoint.app.sh b/docker/dev/entrypoint.app.sh new file mode 100755 index 000000000..aa2144d81 --- /dev/null +++ b/docker/dev/entrypoint.app.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +if [[ "$1" == "composer" ]]; then + exec "$@" +else + wait-for-it db:3306 -t 45 + php artisan migrate --database=mysql_docker_dev + chown -R www-data:www-data storage + exec apache2-foreground +fi \ No newline at end of file diff --git a/docker/dev/entrypoint.node.sh b/docker/dev/entrypoint.node.sh new file mode 100755 index 000000000..e59e1e8a0 --- /dev/null +++ b/docker/dev/entrypoint.node.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +npm install +npm rebuild node-sass + +exec npm run watch \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 352ad5ce9..3330ad5c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -833,6 +833,7 @@ "anymatch": "^2.0.0", "async-each": "^1.0.1", "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", "inherits": "^2.0.3", "is-binary-path": "^1.0.0", @@ -1908,6 +1909,554 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, "fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", diff --git a/readme.md b/readme.md index 62e2aa65d..ede6e5aa5 100644 --- a/readme.md +++ b/readme.md @@ -75,6 +75,29 @@ php artisan db:seed --class=DummyContentSeeder --database=mysql_testing Once done you can run `php vendor/bin/phpunit` in the application root directory to run all tests. +## Getting started with Development using Docker + +This repository ships with a Docker Compose configuration intended for development purposes. It'll build a PHP image with all needed extensions installed and start up a MySQL server and a Node image watching the UI assets. + +To get started, make sure you meet the following requirements: + +- Docker and Docker Compose are installed +- Your user is part of the `docker` group +- Composer is installed + +If all the conditions are met, you can proceed with the following steps: + +1. Install Composer dependencies with **`docker-compose run app composer install`** (first time can take a while because the image has to be built) +2. **Copy `.env.example.docker-development` to `.env`** and change `APP_KEY` to a random 32 char string +3. Make sure **port 8080 is unused** *or else* change `DEV_PORT` to a free port on your host +4. **Run `chgrp -R docker storage`**. The development container will chown the `storage` directory to the `www-data` user inside the container so BookStack can write to it. You need to change the group to your host's `docker` group here to not lose access to the `storage` directory +5. **Run `docker-compose up`** and wait until all database migrations have been done +6. **If you're starting the server for the first time**, seed the database in a separate terminal session: + ```php + docker-compose exec app php artisan db:seed --class=DummyContentSeeder --database=mysql_docker_dev + ``` +7. You can now login with `admin@test.local` and `admin` as password on `localhost:8080` (or another port if specified) + ## Translations All text strings can be found in the `resources/lang` folder where each language option has its own folder. To add a new language you should copy the `en` folder to an new folder (eg. `fr` for french) then go through and translate all text strings in those files, leaving the keys and file-names intact. If a language string is missing then the `en` translation will be used. To show the language option in the user preferences language drop-down you will need to add your language to the options found at the bottom of the `resources/lang/en/settings.php` file. A system-wide language can also be set in the `.env` file like so: `APP_LANG=en`. diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index 3d36d85b2..42b44c152 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -1,8 +1,8 @@ users; + if (count($adminUsers) > 1) { + foreach ($adminUsers->splice(1) as $user) { + $user->delete(); + } + } + // Ensure we currently only have 1 admin user $this->assertEquals(1, $adminRole->users()->count()); $user = $adminRole->users->first();