From a5133e0f9bec5506f2f10ba2260e892a07ec23ad Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Sun, 17 Mar 2019 23:47:30 +0100 Subject: [PATCH 01/36] Use libravatar as drop-in replacement for gravatar Since libravatar got a default fallback to Gravatar and in generell allows federated image hosting for avatars this shouldn't break any existing implementations. The federation functionality is not added yet. This would require to use the libravatar library. Details: https://wiki.libravatar.org/api/ Signed-off-by: Sheogorath --- lib/letter-avatars.js | 2 +- test/letter-avatars.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/letter-avatars.js b/lib/letter-avatars.js index a5dd820..935cc1b 100644 --- a/lib/letter-avatars.js +++ b/lib/letter-avatars.js @@ -37,7 +37,7 @@ exports.generateAvatarURL = function (name, email = '', big = true) { let hexDigest = hash.digest('hex') if (email !== '' && config.allowGravatar) { - photo = 'https://www.gravatar.com/avatar/' + hexDigest; + photo = 'https://cdn.libravatar.org/avatar/' + hexDigest; if (big) { photo += '?s=400' } else { diff --git a/test/letter-avatars.js b/test/letter-avatars.js index c0e967e..8cc32d8 100644 --- a/test/letter-avatars.js +++ b/test/letter-avatars.js @@ -19,8 +19,8 @@ describe('generateAvatarURL() gravatar enabled', function () { }) it('should return correct urls', function () { - assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels', 'hello@dsprenkels.com', true), 'https://www.gravatar.com/avatar/d41b5f3508cc3f31865566a47dd0336b?s=400') - assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels', 'hello@dsprenkels.com', false), 'https://www.gravatar.com/avatar/d41b5f3508cc3f31865566a47dd0336b?s=96') + assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels', 'hello@dsprenkels.com', true), 'https://cdn.libravatar.org/avatar/d41b5f3508cc3f31865566a47dd0336b?s=400') + assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels', 'hello@dsprenkels.com', false), 'https://cdn.libravatar.org/avatar/d41b5f3508cc3f31865566a47dd0336b?s=96') }) it('should return correct urls for names with spaces', function () { From d167f7b09257f9bcf663eec0d3f51b0413aac797 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Sun, 24 Mar 2019 23:11:48 +0100 Subject: [PATCH 02/36] Fix capital letters in language selection Vladan[1] gave a hint about some minor problems with the capitalization of language names. This patch should fix most of them. and removes some "language" prefix and suffixes which are not needed to make clear what people are selecting here. [1]: https://github.com/cvladan Signed-off-by: Sheogorath --- public/views/index/body.ejs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs index 3b8ef62..9b871f8 100644 --- a/public/views/index/body.ejs +++ b/public/views/index/body.ejs @@ -135,20 +135,20 @@ - + - - + + - + - + - +

<%- __('Powered by %s', 'CodiMD') %> | <%= __('Releases') %>| <%= __('Source Code') %><% if(privacyStatement) { %> | <%= __('Privacy') %><% } %><% if(termsOfUse) { %> | <%= __('Terms of Use') %><% } %> From 1544b45af5435fde1190634dc44b65ca6ab55238 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Mon, 25 Mar 2019 17:41:51 +0100 Subject: [PATCH 03/36] Move upload button into toolbar Currently we have the odd situation to have two toolbars. One inside the header and one in the editor. Since we only show the image upload button when the editor is visible we should move the upload button into the editor toolbar. This patch does this by adding the image upload button besides the image tag button. Signed-off-by: Sheogorath --- public/js/index.js | 15 --------------- public/js/lib/editor/index.js | 9 +++++++++ public/js/lib/editor/toolbar.html | 3 +++ public/js/lib/editor/ui-elements.js | 3 +-- public/views/codimd/header.ejs | 6 ------ 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/public/js/index.js b/public/js/index.js index 76f952c..c59c94d 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -304,7 +304,6 @@ var editor = editorInstance.init(textit) // FIXME: global referncing in jquery-textcomplete patch window.editor = editor -var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor) defaultTextHeight = parseInt($('.CodeMirror').css('line-height')) // initalize ui reference @@ -801,7 +800,6 @@ function changeMode (type) { editor.getInputField().blur() } if (appState.currentMode === modeType.edit || appState.currentMode === modeType.both) { - ui.toolbar.uploadImage.fadeIn() // add and update status bar if (!editorInstance.statusBar) { editorInstance.addStatusBar() @@ -814,8 +812,6 @@ function changeMode (type) { // work around foldGutter might not init properly editor.setOption('foldGutter', false) editor.setOption('foldGutter', true) - } else { - ui.toolbar.uploadImage.fadeOut() } if (appState.currentMode !== modeType.edit) { $(document.body).css('background-color', 'white') @@ -1051,17 +1047,6 @@ ui.toolbar.import.snippet.click(function () { ui.spinner.hide() }) }) -// import from clipboard -ui.toolbar.import.clipboard.click(function () { - // na -}) -// upload image -ui.toolbar.uploadImage.bind('change', function (e) { - var files = e.target.files || e.dataTransfer.files - e.dataTransfer = {} - e.dataTransfer.files = files - inlineAttach.onDrop(e) -}) // toc ui.toc.dropdown.click(function (e) { e.stopPropagation() diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js index f05d01b..17d30ca 100644 --- a/public/js/lib/editor/index.js +++ b/public/js/lib/editor/index.js @@ -138,6 +138,7 @@ export default class Editor { } addToolBar () { + var inlineAttach = inlineAttachment.editors.codemirror4.attach(this.editor) this.toolBar = $(toolBarTemplate) this.toolbarPanel = this.editor.addPanel(this.toolBar[0], { position: 'top' @@ -157,6 +158,7 @@ export default class Editor { var makeTable = $('#makeTable') var makeLine = $('#makeLine') var makeComment = $('#makeComment') + var uploadImage = $('#uploadImage') makeBold.click(() => { utils.wrapTextWith(this.editor, this.editor, '**') @@ -217,6 +219,13 @@ export default class Editor { makeComment.click(() => { utils.insertText(this.editor, '> []') }) + uploadImage.bind('change', function (e) { + console.log("tiggered") + var files = e.target.files || e.dataTransfer.files + e.dataTransfer = {} + e.dataTransfer.files = files + inlineAttach.onDrop(e) + }) } addStatusBar () { diff --git a/public/js/lib/editor/toolbar.html b/public/js/lib/editor/toolbar.html index a2ac476..9ea1aac 100644 --- a/public/js/lib/editor/toolbar.html +++ b/public/js/lib/editor/toolbar.html @@ -34,6 +34,9 @@ + + + diff --git a/public/js/lib/editor/ui-elements.js b/public/js/lib/editor/ui-elements.js index 29a3778..ce19436 100644 --- a/public/js/lib/editor/ui-elements.js +++ b/public/js/lib/editor/ui-elements.js @@ -35,8 +35,7 @@ export const getUIElements = () => ({ edit: $('.ui-edit'), view: $('.ui-view'), both: $('.ui-both'), - night: $('.ui-night'), - uploadImage: $('.ui-upload-image') + night: $('.ui-night') }, infobar: { lastchange: $('.ui-lastchange'), diff --git a/public/views/codimd/header.ejs b/public/views/codimd/header.ejs index b83838e..6bf3780 100644 --- a/public/views/codimd/header.ejs +++ b/public/views/codimd/header.ejs @@ -15,9 +15,6 @@ CodiMD

diff --git a/public/views/shared/help-modal.ejs b/public/views/shared/help-modal.ejs index ec35b0e..ab9f6b7 100644 --- a/public/views/shared/help-modal.ejs +++ b/public/views/shared/help-modal.ejs @@ -15,7 +15,7 @@

<%= __('Contacts') %>

- <%= __('Report an issue') %> + <%= __('Report an issue') %>
<%= __('Meet us on %s', 'Matrix') %>
From 982a12f569dbdd7775ba568ef9aa4d76fc251ab5 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Thu, 28 Mar 2019 22:36:26 +0100 Subject: [PATCH 05/36] Fix some remaining references to the old repository Signed-off-by: Sheogorath --- README.md | 6 +++--- docs/guides/s3-image-upload.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e6a2e9b..23b5d94 100644 --- a/README.md +++ b/README.md @@ -373,9 +373,9 @@ See more at [http://operational-transformation.github.io/](http://operational-tr [matrix.org-image]: https://img.shields.io/badge/Matrix.org-%23CodiMD@matrix.org-green.svg [matrix.org-url]: https://riot.im/app/#/room/#codimd:matrix.org -[travis-image]: https://travis-ci.org/hackmdio/codimd.svg?branch=master -[travis-url]: https://travis-ci.org/hackmdio/codimd -[github-version-badge]: https://img.shields.io/github/release/hackmdio/codimd.svg +[travis-image]: https://travis-ci.org/codimd/server.svg?branch=master +[travis-url]: https://travis-ci.org/codimd/server +[github-version-badge]: https://img.shields.io/github/release/codimd/server.svg [github-release-page]: https://github.com/codimd/server/releases [github-release-feed]: https://github.com/codimd/server/releases.atom [poeditor-image]: https://img.shields.io/badge/POEditor-translate-blue.svg diff --git a/docs/guides/s3-image-upload.md b/docs/guides/s3-image-upload.md index dc5e420..40ab868 100644 --- a/docs/guides/s3-image-upload.md +++ b/docs/guides/s3-image-upload.md @@ -76,7 +76,7 @@ } ``` -9. In additional to edit `config.json` directly, you could also try [environment variable](https://github.com/hackmdio/hackmd#environment-variables-will-overwrite-other-server-configs). +9. In additional to edit `config.json` directly, you could also try [environment variable](https://github.com/codimd/server#environment-variables-will-overwrite-other-server-configs). ## Related Tools From a5836dd52fd728cd2e78e566aa5d105ddb908444 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Thu, 28 Mar 2019 22:42:58 +0100 Subject: [PATCH 06/36] Fix container image links Signed-off-by: Sheogorath --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23b5d94..0f6c386 100644 --- a/README.md +++ b/README.md @@ -105,12 +105,12 @@ For all further details, please check out the offical CodiMD [K8s helm chart](h **Debian-based version:** -[![latest](https://images.microbadger.com/badges/version/hackmdio/hackmd:latest.svg)](https://microbadger.com/images/hackmdio/hackmd "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/hackmdio/hackmd:latest.svg)](https://microbadger.com/images/hackmdio/hackmd "Get your own image badge on microbadger.com") +[![Docker Repository on Quay](https://quay.io/repository/codimd/server/status "Docker Repository on Quay")](https://quay.io/repository/codimd/server) **Alpine-based version:** -[![alpine](https://images.microbadger.com/badges/version/hackmdio/hackmd:alpine.svg)](https://microbadger.com/images/hackmdio/hackmd:alpine "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/hackmdio/hackmd:alpine.svg)](https://microbadger.com/images/hackmdio/hackmd:alpine "Get your own image badge on microbadger.com") +[![Docker Repository on Quay](https://quay.io/repository/codimd/server/status "Docker Repository on Quay")](https://quay.io/repository/codimd/server) The easiest way to setup CodiMD using docker are using the following three commands: From e982ef686cf052b5110df417451f9e8a0e32d4e4 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Thu, 28 Mar 2019 22:44:32 +0100 Subject: [PATCH 07/36] Add note about changing the upstream repository People who want to get the latest and greatest version of CodiMD should be aware of the repository change. This upgrade note, will hopefully help. Signed-off-by: Sheogorath --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0f6c386..75f11e8 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ Install CodiMD on [Cloudron](https://cloudron.io): ## Native setup +:warning: When you are still running from the old repository, please run: `git remote set-url origin https://github.com/codimd/server.git` :warning: + If you are upgrading CodiMD from an older version, follow these steps: 1. Fully stop your old server first (important) From 974dc8fc210f31d76e4354bea2468dd9045bbcff Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Thu, 28 Mar 2019 23:49:42 +0100 Subject: [PATCH 08/36] Update maintainers in package.json Signed-off-by: Sheogorath --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 845bc39..7178d46 100644 --- a/package.json +++ b/package.json @@ -149,12 +149,13 @@ "homepage": "https://codimd.org", "maintainers": [ { - "name": "Max Wu", - "email": "jackymaxj@gmail.com" + "name": "Claudius Coenen", + "url": "https://www.claudiuscoenen.de/" }, { "name": "Christoph (Sheogorath) Kern", - "email": "codimd@sheogorath.shivering-isles.com" + "email": "codimd@sheogorath.shivering-isles.com", + "url": "https://shivering-isles.com" } ], "repository": { From 9941d5613b2832835a71f8ed6cd1c5ab3629e5f5 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Fri, 29 Mar 2019 12:29:43 +0100 Subject: [PATCH 09/36] Fix button background color When introducing night mode the rule for the background of the view switches seems to be generated from the view button. This patch should change the introduced rule to fix for all default buttons. Signed-off-by: Sheogorath --- public/css/index.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/css/index.css b/public/css/index.css index d9ebf6d..c304a96 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -385,7 +385,7 @@ div[contenteditable]:empty:not(:focus):before{ color: #eee; } -.night .btn.btn-default.ui-view.active{ +.night .btn.btn-default.active{ background: #202020; } From 16d84926f98c4beb4b615c85f882d046d6f372f4 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Fri, 29 Mar 2019 12:31:12 +0100 Subject: [PATCH 10/36] Fix logo URL in app.json Signed-off-by: Sheogorath --- app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.json b/app.json index b580d98..0b5a515 100644 --- a/app.json +++ b/app.json @@ -8,7 +8,7 @@ ], "website": "https://codimd.org", "repository": "https://github.com/codimd/server", - "logo": "https://github.com/hackmdio/codimd/raw/master/public/codimd-icon-1024.png", + "logo": "https://github.com/codimd/server/raw/master/public/codimd-icon-1024.png", "success_url": "/", "env": { "BUILD_ASSETS": { From 4ffeab6129778ae539934dabbb3825de92199251 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Fri, 29 Mar 2019 12:33:14 +0100 Subject: [PATCH 11/36] Release version 1.3.2 Signed-off-by: Sheogorath --- package.json | 2 +- public/docs/release-notes.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7178d46..538a320 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CodiMD", - "version": "1.3.1", + "version": "1.3.2", "description": "Realtime collaborative markdown notes on all platforms.", "main": "app.js", "license": "AGPL-3.0", diff --git a/public/docs/release-notes.md b/public/docs/release-notes.md index 6269236..6c30a0f 100644 --- a/public/docs/release-notes.md +++ b/public/docs/release-notes.md @@ -1,6 +1,16 @@ Release Notes === + 1.3.2 2019-03-28 00:00 +--- + +### Announcement +* CodiMD is now running in an own organization. [Check out our vision for the future](https://github.com/codimd/server/issues/10) + +### Fixes +* Update various links to the new repositories +* Fix background color for mode switching button in night mode + 1.3.1 2019-03-23 00:00 --- From 5c607c4f80e2c00699dcd74404b8652e0dfbbe94 Mon Sep 17 00:00:00 2001 From: Claudius Date: Sun, 31 Mar 2019 01:29:34 +0100 Subject: [PATCH 12/36] cleanup of the heroku configuration this removes the general `postinstall` call to `bin/heroku` and instead puts it into a heroku-prebuild hook. At the same time, env vars get updated to use the `CMD` prefix. The configured buildpacks were not used. Finally, npm run build is now automatically done by Heroku. Signed-off-by: Claudius --- app.json | 72 ++++++++++++++++++++++------------------------------ bin/heroku | 10 ++------ package.json | 2 +- 3 files changed, 33 insertions(+), 51 deletions(-) diff --git a/app.json b/app.json index 0b5a515..dcfe67c 100644 --- a/app.json +++ b/app.json @@ -11,10 +11,6 @@ "logo": "https://github.com/codimd/server/raw/master/public/codimd-icon-1024.png", "success_url": "/", "env": { - "BUILD_ASSETS": { - "description": "Our build script variable", - "value": "true" - }, "NPM_CONFIG_PRODUCTION": { "description": "Let npm also install development build tool", "value": "false" @@ -23,137 +19,129 @@ "description": "Specify database type. See sequelize available databases. Default using postgres", "value": "postgres" }, - "HMD_SESSION_SECRET": { + "CMD_SESSION_SECRET": { "description": "Secret used to secure session cookies.", "required": false }, - "HMD_HSTS_ENABLE": { + "CMD_HSTS_ENABLE": { "description": "whether to also use HSTS if HTTPS is enabled", "required": false }, - "HMD_HSTS_MAX_AGE": { + "CMD_HSTS_MAX_AGE": { "description": "max duration, in seconds, to tell clients to keep HSTS status", "required": false }, - "HMD_HSTS_INCLUDE_SUBDOMAINS": { + "CMD_HSTS_INCLUDE_SUBDOMAINS": { "description": "whether to tell clients to also regard subdomains as HSTS hosts", "required": false }, - "HMD_HSTS_PRELOAD": { + "CMD_HSTS_PRELOAD": { "description": "whether to allow at all adding of the site to HSTS preloads (e.g. in browsers)", "required": false }, - "HMD_DOMAIN": { + "CMD_DOMAIN": { "description": "domain name", "required": false }, - "HMD_URL_PATH": { + "CMD_URL_PATH": { "description": "sub url path, like `www.example.com/`", "required": false }, - "HMD_ALLOW_ORIGIN": { + "CMD_ALLOW_ORIGIN": { "description": "domain name whitelist (use comma to separate)", "required": false, "value": "localhost" }, - "HMD_PROTOCOL_USESSL": { + "CMD_PROTOCOL_USESSL": { "description": "set to use ssl protocol for resources path (only applied when domain is set)", "required": false }, - "HMD_URL_ADDPORT": { + "CMD_URL_ADDPORT": { "description": "set to add port on callback url (port 80 or 443 won't applied) (only applied when domain is set)", "required": false }, - "HMD_FACEBOOK_CLIENTID": { + "CMD_FACEBOOK_CLIENTID": { "description": "Facebook API client id", "required": false }, - "HMD_FACEBOOK_CLIENTSECRET": { + "CMD_FACEBOOK_CLIENTSECRET": { "description": "Facebook API client secret", "required": false }, - "HMD_TWITTER_CONSUMERKEY": { + "CMD_TWITTER_CONSUMERKEY": { "description": "Twitter API consumer key", "required": false }, - "HMD_TWITTER_CONSUMERSECRET": { + "CMD_TWITTER_CONSUMERSECRET": { "description": "Twitter API consumer secret", "required": false }, - "HMD_GITHUB_CLIENTID": { + "CMD_GITHUB_CLIENTID": { "description": "GitHub API client id", "required": false }, - "HMD_GITHUB_CLIENTSECRET": { + "CMD_GITHUB_CLIENTSECRET": { "description": "GitHub API client secret", "required": false }, - "HMD_GITLAB_BASEURL": { + "CMD_GITLAB_BASEURL": { "description": "GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional)", "required": false }, - "HMD_GITLAB_CLIENTID": { + "CMD_GITLAB_CLIENTID": { "description": "GitLab API client id", "required": false }, - "HMD_GITLAB_CLIENTSECRET": { + "CMD_GITLAB_CLIENTSECRET": { "description": "GitLab API client secret", "required": false }, - "HMD_GITLAB_SCOPE": { + "CMD_GITLAB_SCOPE": { "description": "GitLab API client scope (optional)", "required": false }, - "HMD_MATTERMOST_BASEURL": { + "CMD_MATTERMOST_BASEURL": { "description": "Mattermost authentication endpoint", "required": false }, - "HMD_MATTERMOST_CLIENTID": { + "CMD_MATTERMOST_CLIENTID": { "description": "Mattermost API client id", "required": false }, - "HMD_MATTERMOST_CLIENTSECRET": { + "CMD_MATTERMOST_CLIENTSECRET": { "description": "Mattermost API client secret", "required": false }, - "HMD_DROPBOX_CLIENTID": { + "CMD_DROPBOX_CLIENTID": { "description": "Dropbox API client id", "required": false }, - "HMD_DROPBOX_CLIENTSECRET": { + "CMD_DROPBOX_CLIENTSECRET": { "description": "Dropbox API client secret", "required": false }, - "HMD_DROPBOX_APP_KEY": { + "CMD_DROPBOX_APP_KEY": { "description": "Dropbox app key (for import/export)", "required": false }, - "HMD_GOOGLE_CLIENTID": { + "CMD_GOOGLE_CLIENTID": { "description": "Google API client id", "required": false }, - "HMD_GOOGLE_CLIENTSECRET": { + "CMD_GOOGLE_CLIENTSECRET": { "description": "Google API client secret", "required": false }, - "HMD_IMGUR_CLIENTID": { + "CMD_IMGUR_CLIENTID": { "description": "Imgur API client id", "required": false }, - "HMD_ALLOW_PDF_EXPORT": { + "CMD_ALLOW_PDF_EXPORT": { "description": "Enable or disable PDF exports", "required": false } }, "addons": [ "heroku-postgresql" - ], - "buildpacks": [ - { - "url": "https://github.com/alex88/heroku-buildpack-vips" - }, - { - "url": "https://github.com/heroku/heroku-buildpack-nodejs" - } ] } diff --git a/bin/heroku b/bin/heroku index 2472734..c9642c2 100755 --- a/bin/heroku +++ b/bin/heroku @@ -2,9 +2,7 @@ set -e -if [ "$BUILD_ASSETS" = true ]; then - # setup config files - cat << EOF > .sequelizerc +cat << EOF > .sequelizerc var path = require('path'); module.exports = { @@ -17,7 +15,7 @@ module.exports = { EOF - cat << EOF > config.json +cat << EOF > config.json { "production": { @@ -25,7 +23,3 @@ EOF } EOF - - # build app - npm run build -fi diff --git a/package.json b/package.json index 538a320..c17c0db 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "jsonlint": "find . -not -path './node_modules/*' -type f -name '*.json' -o -type f -name '*.json.example' | while read json; do echo $json ; jq . $json; done", "standard": "echo 'standard is no longer being used, use `npm run eslint` instead!' && exit 1", "dev": "webpack --config webpack.dev.js --progress --colors --watch", + "heroku-prebuild": "bin/heroku", "build": "webpack --config webpack.prod.js --progress --colors --bail", - "postinstall": "bin/heroku", "start": "sequelize db:migrate && node app.js", "doctoc": "doctoc --title='# Table of Contents' README.md" }, From 962330933da5625b47573fea5c39cbd60ef228a9 Mon Sep 17 00:00:00 2001 From: Sheogorath Date: Sat, 30 Mar 2019 16:53:28 +0100 Subject: [PATCH 13/36] Fix LICENSE content It seems like the license was never correctly filled. This patch updates the LICENSE file to represent members of the community and major code contributors. Signed-off-by: Sheogorath --- LICENSE | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 2def0e8..ccbd997 100644 --- a/LICENSE +++ b/LICENSE @@ -629,8 +629,15 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + CodiMD - Realtime collaborative markdown notes on all platforms. + Copyright (C) 2019 Christoph (Sheogorath) Kern + Copyright (C) 2019 Claudius Coenen + Copyright (C) 2019 Max Wu + Copyright (C) 2017 Yukai Huang + And more can be found on https://github.com/codimd/server/graphs/contributors + Or in the local AUTHORS file + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by @@ -658,4 +665,4 @@ specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see -. \ No newline at end of file +. From edf301cfa320cce48eaf055a57f416bc1aa6ee1e Mon Sep 17 00:00:00 2001 From: Claudius Date: Sun, 31 Mar 2019 16:02:05 +0200 Subject: [PATCH 14/36] splitting README.md into files in /docs for better readability Signed-off-by: Claudius --- README.md | 389 ++---------------- docs/configuration-config-file.md | 64 +++ docs/configuration-env-vars.md | 113 +++++ docs/dev/getting-started.md | 16 + docs/dev/ot.md | 6 + docs/guides/auth/github.md | 6 +- docs/guides/auth/gitlab-self-hosted.md | 6 +- docs/guides/auth/mattermost-self-hosted.md | 8 +- docs/guides/auth/nextcloud.md | 6 +- docs/guides/auth/oauth.md | 12 + docs/guides/auth/saml-onelogin.md | 12 +- docs/guides/auth/twitter.md | 8 +- .../guides/migrations-and-breaking-changes.md | 58 +++ docs/guides/minio-image-upload.md | 10 +- docs/guides/s3-image-upload.md | 12 +- docs/history.md | 39 ++ docs/images/CodiMD-1.3.2-features.png | Bin 0 -> 81971 bytes .../images/auth/application-page.png | Bin .../images/auth/create-oauth-app.png | Bin .../images/auth/create-twitter-app.png | Bin .../auth/gitlab-application-details.png | Bin .../images/auth/gitlab-new-application.png | Bin .../images/auth/gitlab-sign-in.png | Bin .../images/auth/mattermost-enable-oauth2.png | Bin .../images/auth/mattermost-oauth-app-add.png | Bin .../images/auth/mattermost-oauth-app-done.png | Bin .../images/auth/mattermost-oauth-app-form.png | Bin .../auth/nextcloud-oauth2-1-settings.png | Bin .../auth/nextcloud-oauth2-2-client-add.png | Bin .../nextcloud-oauth2-3-clientid-secret.png | Bin .../images/auth/onelogin-add-app.png | Bin .../auth/onelogin-copy-idp-metadata.png | Bin .../images/auth/onelogin-edit-app-name.png | Bin .../images/auth/onelogin-edit-sp-metadata.png | Bin .../images/auth/onelogin-select-template.png | Bin .../images/auth/onelogin-use-dashboard.png | Bin .../auth/register-oauth-application-form.png | Bin .../auth/register-twitter-application.png | Bin .../images/auth/twitter-app-confirmation.png | Bin .../images/auth/twitter-app-keys.png | Bin .../minio-image-upload/create-bucket.png | Bin .../minio-image-upload/create-policy.png | Bin .../minio-image-upload/default-view.png | Bin .../images/minio-image-upload/docker-logs.png | Bin .../minio-image-upload/open-edit-policy.png | Bin .../s3-image-upload/bucket-policy-editor.png | Bin .../s3-image-upload/bucket-property.png | Bin .../images/s3-image-upload/create-bucket.png | Bin .../images/s3-image-upload/custom-policy.png | Bin .../images/s3-image-upload/iam-user.png | Bin .../images/s3-image-upload/review-policy.png | Bin docs/setup/cloudron.md | 5 + docs/setup/docker.md | 22 + docs/setup/heroku.md | 6 + docs/setup/kubernetes.md | 5 + docs/setup/manual-setup.md | 37 ++ 56 files changed, 462 insertions(+), 378 deletions(-) create mode 100644 docs/configuration-config-file.md create mode 100644 docs/configuration-env-vars.md create mode 100644 docs/dev/getting-started.md create mode 100644 docs/dev/ot.md create mode 100644 docs/guides/auth/oauth.md create mode 100644 docs/guides/migrations-and-breaking-changes.md create mode 100644 docs/history.md create mode 100644 docs/images/CodiMD-1.3.2-features.png rename docs/{guides => }/images/auth/application-page.png (100%) rename docs/{guides => }/images/auth/create-oauth-app.png (100%) rename docs/{guides => }/images/auth/create-twitter-app.png (100%) rename docs/{guides => }/images/auth/gitlab-application-details.png (100%) rename docs/{guides => }/images/auth/gitlab-new-application.png (100%) rename docs/{guides => }/images/auth/gitlab-sign-in.png (100%) rename docs/{guides => }/images/auth/mattermost-enable-oauth2.png (100%) rename docs/{guides => }/images/auth/mattermost-oauth-app-add.png (100%) rename docs/{guides => }/images/auth/mattermost-oauth-app-done.png (100%) rename docs/{guides => }/images/auth/mattermost-oauth-app-form.png (100%) rename docs/{guides => }/images/auth/nextcloud-oauth2-1-settings.png (100%) rename docs/{guides => }/images/auth/nextcloud-oauth2-2-client-add.png (100%) rename docs/{guides => }/images/auth/nextcloud-oauth2-3-clientid-secret.png (100%) rename docs/{guides => }/images/auth/onelogin-add-app.png (100%) rename docs/{guides => }/images/auth/onelogin-copy-idp-metadata.png (100%) rename docs/{guides => }/images/auth/onelogin-edit-app-name.png (100%) rename docs/{guides => }/images/auth/onelogin-edit-sp-metadata.png (100%) rename docs/{guides => }/images/auth/onelogin-select-template.png (100%) rename docs/{guides => }/images/auth/onelogin-use-dashboard.png (100%) rename docs/{guides => }/images/auth/register-oauth-application-form.png (100%) rename docs/{guides => }/images/auth/register-twitter-application.png (100%) rename docs/{guides => }/images/auth/twitter-app-confirmation.png (100%) rename docs/{guides => }/images/auth/twitter-app-keys.png (100%) rename docs/{guides => }/images/minio-image-upload/create-bucket.png (100%) rename docs/{guides => }/images/minio-image-upload/create-policy.png (100%) rename docs/{guides => }/images/minio-image-upload/default-view.png (100%) rename docs/{guides => }/images/minio-image-upload/docker-logs.png (100%) rename docs/{guides => }/images/minio-image-upload/open-edit-policy.png (100%) rename docs/{guides => }/images/s3-image-upload/bucket-policy-editor.png (100%) rename docs/{guides => }/images/s3-image-upload/bucket-property.png (100%) rename docs/{guides => }/images/s3-image-upload/create-bucket.png (100%) rename docs/{guides => }/images/s3-image-upload/custom-policy.png (100%) rename docs/{guides => }/images/s3-image-upload/iam-user.png (100%) rename docs/{guides => }/images/s3-image-upload/review-policy.png (100%) create mode 100644 docs/setup/cloudron.md create mode 100644 docs/setup/docker.md create mode 100644 docs/setup/heroku.md create mode 100644 docs/setup/kubernetes.md create mode 100644 docs/setup/manual-setup.md diff --git a/README.md b/README.md index 75f11e8..6e4d349 100644 --- a/README.md +++ b/README.md @@ -7,50 +7,59 @@ CodiMD [![POEditor][poeditor-image]][poeditor-url] CodiMD lets you create real-time collaborative markdown notes on all platforms. -Inspired by Hackpad, with more focus on speed and flexibility, and build from [HackMD](https://hackmd.io) source code. -Feel free to contribute. +It is inspired by Hackpad, Etherpad and similar collaborative editors. This +project originated with the team at [HackMD](https://hackmd.io) and now forked +into its own organisation. [A longer writeup can be read in the history doc](docs/history.md). -Thanks for using! :smile: +![CodiMD 1.3.2 with its feature demonstration page open](docs/images/CodiMD-1.3.2-features.png) - - -# Table of Contents -- [HackMD CE became CodiMD](#hackmd-ce-became-codimd) -- [Browsers Requirement](#browsers-requirement) -- [Installation](#installation) - - [Getting started (Native install)](#getting-started-native-install) - - [Prerequisite](#prerequisite) - - [Instructions](#instructions) - - [Heroku Deployment](#heroku-deployment) - - [Kubernetes](#kubernetes) - - [CodiMD by docker container](#codimd-by-docker-container) - - [Cloudron](#cloudron) -- [Upgrade](#upgrade) - - [Native setup](#native-setup) -- [Configuration](#configuration) - - [Environment variables (will overwrite other server configs)](#environment-variables-will-overwrite-other-server-configs) - - [Application settings `config.json`](#application-settings-configjson) - - [Third-party integration API key settings](#third-party-integration-api-key-settings) - - [Third-party integration OAuth callback URLs](#third-party-integration-oauth-callback-urls) -- [Developer Notes](#developer-notes) - - [Structure](#structure) - - [Operational Transformation](#operational-transformation) -- [License](#license) +## Community and Contributions - +We welcome contributions! There's a lot to do. If you would like to report bugs +the [issue tracker](github-issue-tracker) is the right place. If you want to +help translating, find us on [POEditor](poeditor-url). To get started developing, +take a look at the [docs/dev](docs/dev) directory. In any case: come talk to us, +we'll be delighted to help you with the first steps. -# HackMD CE became CodiMD +To stay up to date with your installation it's recommended to join our [Matrix channel][matrix.org-url] or subscribe to the [release feed][github-release-feed]. -CodiMD was recently renamed from its former name was HackMD. CodiMD is the free software version of HackMD. It was the original Version of HackMD. The HackMD team initiated CodiMD and provided a solid code base. Due to the need of paying bills, A fork was created and called HackMD EE, which is a SaaS (Software as a Service) product available at [hackmd.io](https://hackmd.io). -We decided to change the name to break the confusion between HackMD and CodiMD, formally known as HackMD CE, as it never was an open core project. +## Installation / Upgrading -Just to more confusion: We are still friends with HackMD :heart: +You can run CodiMD in a number of ways, and we created setup instructions for +all of these: -*For the whole renaming story, see the [related issue](https://github.com/hackmdio/hackmd/issues/720)* +* [Docker](docs/setup/docker.md) +* [Kubernetes](docs/setup/kubernetes.md) +* [Cloudron](docs/setup/cloudron.md) +* [Heroku](docs/setup/heroku.md) +* [manual setup](docs/setup/manual-setup.md) -# Browsers Requirement +If you do not wish to run your own setup, you can find a commercial offering at +https://hackmd.io. This is not the same codebase as this one, but it is a very +similar project. + + +## Configuration + +Theres two main ways to configure your CodiMD instance: +[Config file](docs/configuration-config-file.md) or +[environment variables](docs/configuration-env-vars.md). You can choose what +works best for you. + +CodiMD can integrate with + +* facebook, twitter, github, gitlab, mattermost, dropbox, google, ldap, saml and [oauth2](docs/guides/auth/oauth.md) **for login** +* imgur, s3, minio, azure **for image/attachment storage** (files can also be local!) +* dropbox **for export and import** + +More info about that can be found in the configuration docs above. + + +## Browser support + +To use CodiMD, your browser should match or exceed these versions: - ![Chrome](http://browserbadge.com/chrome/47/18px) Chrome >= 47, Chrome for Android >= 47 - ![Safari](http://browserbadge.com/safari/9/18px) Safari >= 9, iOS Safari >= 8.4 @@ -59,319 +68,10 @@ Just to more confusion: We are still friends with HackMD :heart: - ![Opera](http://browserbadge.com/opera/34/18px) Opera >= 34, Opera Mini not supported - Android Browser >= 4.4 -# Installation - -## Getting started (Native install) - -### Prerequisite - -- Node.js 6.x or up (test up to 7.5.0) and <10.x -- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8` -- npm (and its dependencies, especially [uWebSockets](https://github.com/uWebSockets/uWebSockets#nodejs-developers), [node-gyp](https://github.com/nodejs/node-gyp#installation)) -- `libssl-dev` for building scrypt (see [here](https://github.com/ml1nk/node-scrypt/blob/master/README.md#installation-instructions) for further information) -- For **building** CodiMD we recommend to use a machine with at least **2GB** RAM - -### Instructions - -1. Download a release and unzip or clone into a directory -2. Enter the directory and type `bin/setup`, which will install npm dependencies and create configs. The setup script is written in Bash, you would need bash as a prerequisite. -3. Setup the configs, see more below -4. Setup environment variables which will overwrite the configs -5. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development) -6. Modify the file named `.sequelizerc`, change the value of the variable `url` with your db connection string - For example: `postgres://username:password@localhost:5432/codimd` -7. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema -8. Run the server as you like (node, forever, pm2) - -To stay up to date with your installation it's recommended to join our [Matrix channel][matrix.org-url] or subscribe to the [release feed][github-release-feed]. - -## Heroku Deployment - -You can quickly setup a sample Heroku CodiMD application by clicking the button below. - -[![Deploy on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/codimd/server/tree/master) - -If you deploy it without the button, keep in mind to use the right buildpacks. For details check `app.json`. - -## Kubernetes - -To install use `helm install stable/hackmd`. - -For all further details, please check out the offical CodiMD [K8s helm chart](https://github.com/kubernetes/charts/tree/master/stable/hackmd). - -## CodiMD by docker container -[![Try in PWD](https://cdn.rawgit.com/play-with-docker/stacks/cff22438/assets/images/button.png)](http://play-with-docker.com?stack=https://github.com/codimd/container/raw/master/docker-compose.yml&stack_name=codimd) - - -**Debian-based version:** - -[![Docker Repository on Quay](https://quay.io/repository/codimd/server/status "Docker Repository on Quay")](https://quay.io/repository/codimd/server) - - -**Alpine-based version:** - -[![Docker Repository on Quay](https://quay.io/repository/codimd/server/status "Docker Repository on Quay")](https://quay.io/repository/codimd/server) - -The easiest way to setup CodiMD using docker are using the following three commands: - -```console -git clone https://github.com/codimd/container.git -cd codimd-container -docker-compose up -``` -Read more about it in the [container repository…](https://github.com/codimd/container) - -## Cloudron - -Install CodiMD on [Cloudron](https://cloudron.io): - -[![Install](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=io.hackmd.cloudronapp) - -# Upgrade - -## Native setup - -:warning: When you are still running from the old repository, please run: `git remote set-url origin https://github.com/codimd/server.git` :warning: - -If you are upgrading CodiMD from an older version, follow these steps: - -1. Fully stop your old server first (important) -2. `git pull` or do whatever that updates the files -3. `npm install` to update dependencies -4. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development) -5. Modify the file named `.sequelizerc`, change the value of the variable `url` with your db connection string - For example: `postgres://username:password@localhost:5432/codimd` -6. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema -7. Start your whole new server! - -To stay up to date with your installation it's recommended to join our [Matrix channel][matrix.org-url] or subscribe to the [release feed][github-release-feed]. - -* **migrate-to-1.1.0** - -We deprecated the older lower case config style and moved on to camel case style. Please have a look at the current `config.json.example` and check the warnings on startup. - -*Notice: This is not a breaking change right now but in the future* - -* [**migration-to-0.5.0**](https://github.com/hackmdio/migration-to-0.5.0) - -We don't use LZString to compress socket.io data and DB data after version 0.5.0. -Please run the migration tool if you're upgrading from the old version. - -* [**migration-to-0.4.0**](https://github.com/hackmdio/migration-to-0.4.0) - -We've dropped MongoDB after version 0.4.0. -So here is the migration tool for you to transfer the old DB data to the new DB. -This tool is also used for official service. - -# Configuration - -There are some config settings you need to change in the files below. - -``` -./config.json ----application settings -``` - -## Environment variables (will overwrite other server configs) - -| variables | example values | description | -| --------- | ------ | ----------- | -| `NODE_ENV` | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) | -| `DEBUG` | `true` or `false` | set debug mode; show more logs | -| `CMD_CONFIG_FILE` | `/path/to/config.json` | optional override for the path to CodiMD's config file | -| `CMD_DOMAIN` | `codimd.org` | domain name | -| `CMD_URL_PATH` | `codimd` | sub URL path, like `www.example.com/` | -| `CMD_HOST` | `localhost` | host to listen on | -| `CMD_PORT` | `80` | web app port | -| `CMD_PATH` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `CMD_HOST` and `CMD_PORT` are ignored) | -| `CMD_LOGLEVEL` | `info` | Defines what kind of logs are provided to stdout. | -| `CMD_ALLOW_ORIGIN` | `localhost, codimd.org` | domain name whitelist (use comma to separate) | -| `CMD_PROTOCOL_USESSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) | -| `CMD_URL_ADDPORT` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) | -| `CMD_USECDN` | `true` or `false` | set to use CDN resources or not (default is `true`) | -| `CMD_ALLOW_ANONYMOUS` | `true` or `false` | set to allow anonymous usage (default is `true`) | -| `CMD_ALLOW_ANONYMOUS_EDITS` | `true` or `false` | if `allowAnonymous` is `true`, allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) | -| `CMD_ALLOW_FREEURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL | -| `CMD_FORBIDDEN_NODE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` | -| `CMD_DEFAULT_PERMISSION` | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) | -| `CMD_DB_URL` | `mysql://localhost:3306/database` | set the database URL | -| `CMD_SESSION_SECRET` | no example | Secret used to sign the session cookie. If non is set, one will randomly generated on startup | -| `CMD_SESSION_LIFE` | `1209600000` | Session life time. (milliseconds) | -| `CMD_FACEBOOK_CLIENTID` | no example | Facebook API client id | -| `CMD_FACEBOOK_CLIENTSECRET` | no example | Facebook API client secret | -| `CMD_TWITTER_CONSUMERKEY` | no example | Twitter API consumer key | -| `CMD_TWITTER_CONSUMERSECRET` | no example | Twitter API consumer secret | -| `CMD_GITHUB_CLIENTID` | no example | GitHub API client id | -| `CMD_GITHUB_CLIENTSECRET` | no example | GitHub API client secret | -| `CMD_GITLAB_SCOPE` | `read_user` or `api` | GitLab API requested scope (default is `api`) (GitLab snippet import/export need `api` scope) | -| `CMD_GITLAB_BASEURL` | no example | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) | -| `CMD_GITLAB_CLIENTID` | no example | GitLab API client id | -| `CMD_GITLAB_CLIENTSECRET` | no example | GitLab API client secret | -| `CMD_GITLAB_VERSION` | no example | GitLab API version (v3 or v4) | -| `CMD_MATTERMOST_BASEURL` | no example | Mattermost authentication endpoint for versions below 5.0. For Mattermost version 5.0 and above, see [guide](docs/guides/auth/mattermost-self-hosted.md). | -| `CMD_MATTERMOST_CLIENTID` | no example | Mattermost API client id | -| `CMD_MATTERMOST_CLIENTSECRET` | no example | Mattermost API client secret | -| `CMD_DROPBOX_CLIENTID` | no example | Dropbox API client id | -| `CMD_DROPBOX_CLIENTSECRET` | no example | Dropbox API client secret | -| `CMD_GOOGLE_CLIENTID` | no example | Google API client id | -| `CMD_GOOGLE_CLIENTSECRET` | no example | Google API client secret | -| `CMD_LDAP_URL` | `ldap://example.com` | URL of LDAP server | -| `CMD_LDAP_BINDDN` | no example | bindDn for LDAP access | -| `CMD_LDAP_BINDCREDENTIALS` | no example | bindCredentials for LDAP access | -| `CMD_LDAP_SEARCHBASE` | `o=users,dc=example,dc=com` | LDAP directory to begin search from | -| `CMD_LDAP_SEARCHFILTER` | `(uid={{username}})` | LDAP filter to search with | -| `CMD_LDAP_SEARCHATTRIBUTES` | `displayName, mail` | LDAP attributes to search with (use comma to separate) | -| `CMD_LDAP_USERIDFIELD` | `uidNumber` or `uid` or `sAMAccountName` | The LDAP field which is used uniquely identify a user on CodiMD | -| `CMD_LDAP_USERNAMEFIELD` | Fallback to userid | The LDAP field which is used as the username on CodiMD | -| `CMD_LDAP_TLS_CA` | `server-cert.pem, root.pem` | Root CA for LDAP TLS in PEM format (use comma to separate) | -| `CMD_LDAP_PROVIDERNAME` | `My institution` | Optional name to be displayed at login form indicating the LDAP provider | -| `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](docs/guides/auth/saml-onelogin.md). | -| `CMD_SAML_IDPCERT` | `/path/to/cert.pem` | certificate file path of IdP in PEM format | -| `CMD_SAML_ISSUER` | no example | identity of the service provider (optional, default: serverurl)" | -| `CMD_SAML_IDENTIFIERFORMAT` | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) | -| `CMD_SAML_GROUPATTRIBUTE` | `memberOf` | attribute name for group list (optional) | -| `CMD_SAML_REQUIREDGROUPS` | `Hackmd-users` | group names that allowed (use vertical bar to separate) (optional) | -| `CMD_SAML_EXTERNALGROUPS` | `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) | -| `CMD_SAML_ATTRIBUTE_ID` | `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) | -| `CMD_SAML_ATTRIBUTE_USERNAME` | `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) | -| `CMD_SAML_ATTRIBUTE_EMAIL` | `mail` | attribute map for `email` (optional, default: NameID of SAML response if `CMD_SAML_IDENTIFIERFORMAT` is default) | -| `CMD_OAUTH2_USER_PROFILE_URL` | `https://example.com` | where retrieve information about a user after succesful login. Needs to output JSON. (no default value) Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details on all of the `CMD_OAUTH2...` options. | -| `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR` | `name` | where to find the username in the JSON from the user profile URL. (no default value)| -| `CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR` | `display-name` | where to find the display-name in the JSON from the user profile URL. (no default value) | -| `CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR` | `email` | where to find the email address in the JSON from the user profile URL. (no default value) | -| `CMD_OAUTH2_TOKEN_URL` | `https://example.com` | sometimes called token endpoint, please refer to the documentation of your OAuth2 provider (no default value) | -| `CMD_OAUTH2_AUTHORIZATION_URL` | `https://example.com` | authorization URL of your provider, please refer to the documentation of your OAuth2 provider (no default value) | -| `CMD_OAUTH2_CLIENT_ID` | `afae02fckafd...` | you will get this from your OAuth2 provider when you register CodiMD as OAuth2-client, (no default value) | -| `CMD_OAUTH2_CLIENT_SECRET` | `afae02fckafd...` | you will get this from your OAuth2 provider when you register CodiMD as OAuth2-client, (no default value) | -| `CMD_OAUTH2_PROVIDERNAME` | `My institution` | Optional name to be displayed at login form indicating the oAuth2 provider | -| `CMD_IMGUR_CLIENTID` | no example | Imgur API client id | -| `CMD_EMAIL` | `true` or `false` | set to allow email signin | -| `CMD_ALLOW_PDF_EXPORT` | `true` or `false` | Enable or disable PDF exports | -| `CMD_ALLOW_EMAIL_REGISTER` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) | -| `CMD_ALLOW_GRAVATAR` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance | -| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio` or `filesystem` | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md) | -| `CMD_S3_ACCESS_KEY_ID` | no example | AWS access key id | -| `CMD_S3_SECRET_ACCESS_KEY` | no example | AWS secret key | -| `CMD_S3_REGION` | `ap-northeast-1` | AWS S3 region | -| `CMD_S3_BUCKET` | no example | AWS S3 bucket name | -| `CMD_MINIO_ACCESS_KEY` | no example | Minio access key | -| `CMD_MINIO_SECRET_KEY` | no example | Minio secret key | -| `CMD_MINIO_ENDPOINT` | `minio.example.org` | Address of your Minio endpoint/instance | -| `CMD_MINIO_PORT` | `9000` | Port that is used for your Minio instance | -| `CMD_MINIO_SECURE` | `true` | If set to `true` HTTPS is used for Minio | -| `CMD_AZURE_CONNECTION_STRING` | no example | Azure Blob Storage connection string | -| `CMD_AZURE_CONTAINER` | no example | Azure Blob Storage container name (automatically created if non existent) | -| `CMD_HSTS_ENABLE` | ` true` | set to enable [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) if HTTPS is also enabled (default is ` true`) | -| `CMD_HSTS_INCLUDE_SUBDOMAINS` | `true` | set to include subdomains in HSTS (default is `true`) | -| `CMD_HSTS_MAX_AGE` | `31536000` | max duration in seconds to tell clients to keep HSTS status (default is a year) | -| `CMD_HSTS_PRELOAD` | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) | -| `CMD_CSP_ENABLE` | `true` | whether to enable Content Security Policy (directives cannot be configured with environment variables) | -| `CMD_CSP_REPORTURI` | `https://.report-uri.com/r/d/csp/enforce` | Allows to add a URL for CSP reports in case of violations | -| `CMD_SOURCE_URL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | - -***Note:** Due to the rename process we renamed all `HMD_`-prefix variables to be `CMD_`-prefixed. The old ones continue to work.* - -## Application settings `config.json` - -| variables | example values | description | -| --------- | ------ | ----------- | -| `debug` | `true` or `false` | set debug mode, show more logs | -| `domain` | `localhost` | domain name | -| `urlPath` | `codimd` | sub URL path, like `www.example.com/` | -| `host` | `localhost` | host to listen on | -| `port` | `80` | web app port | -| `path` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `host` and `port` are ignored) | -| `loglevel` | `info` | Defines what kind of logs are provided to stdout. | -| `allowOrigin` | `['localhost']` | domain name whitelist | -| `useSSL` | `true` or `false` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) | -| `hsts` | `{"enable": true, "maxAgeSeconds": 31536000, "includeSubdomains": true, "preload": true}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) | -| `csp` | `{"enable": true, "directives": {"scriptSrc": "trustworthy-scripts.example.com"}, "upgradeInsecureRequests": "auto", "addDefaults": true}` | Configures [Content Security Policy](https://helmetjs.github.io/docs/csp/). Directives are passed to Helmet - see [their documentation](https://helmetjs.github.io/docs/csp/) for more information on the format. Some defaults are added to the configured values so that the application doesn't break. To disable this behaviour, set `addDefaults` to `false`. Further, if `usecdn` is on, some CDN locations are allowed too. By default (`auto`), insecure (HTTP) requests are upgraded to HTTPS via CSP if `useSSL` is on. To change this behaviour, set `upgradeInsecureRequests` to either `true` or `false`. | -| `protocolUseSSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) | -| `urlAddPort` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) | -| `useCDN` | `true` or `false` | set to use CDN resources or not (default is `true`) | -| `allowAnonymous` | `true` or `false` | set to allow anonymous usage (default is `true`) | -| `allowAnonymousEdits` | `true` or `false` | if `allowAnonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) | -| `allowFreeURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL | -| `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` | -| `defaultPermission` | `freely`, `editable`, `limited`, `locked`, `protected` or `private` | set notes default permission (only applied on signed users) | -| `dbURL` | `mysql://localhost:3306/database` | set the db URL; if set, then db config (below) won't be applied | -| `db` | `{ "dialect": "sqlite", "storage": "./db.codimd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) | -| `sslKeyPath` | `./cert/client.key` | SSL key path1 (only need when you set `useSSL`) | -| `sslCertPath` | `./cert/codimd_io.crt` | SSL cert path1 (only need when you set `useSSL`) | -| `sslCAPath` | `['./cert/COMODORSAAddTrustCA.crt']` | SSL ca chain1 (only need when you set `useSSL`) | -| `dhParamPath` | `./cert/dhparam.pem` | SSL dhparam path1 (only need when you set `useSSL`) | -| `tmpPath` | `./tmp/` | temp directory path1 | -| `defaultNotePath` | `./public/default.md` | default note file path1 | -| `docsPath` | `./public/docs` | docs directory path1 | -| `viewPath` | `./public/views` | template directory path1 | -| `uploadsPath` | `./public/uploads` | uploads directory1 - needs to be persistent when you use imageUploadType `filesystem` | -| `sessionName` | `connect.sid` | cookie session name | -| `sessionSecret` | `secret` | cookie session secret | -| `sessionLife` | `14 * 24 * 60 * 60 * 1000` | cookie session life | -| `staticCacheTime` | `1 * 24 * 60 * 60 * 1000` | static file cache time | -| `heartbeatInterval` | `5000` | socket.io heartbeat interval | -| `heartbeatTimeout` | `10000` | socket.io heartbeat timeout | -| `documentMaxLength` | `100000` | note max length | -| `email` | `true` or `false` | set to allow email signin | -| `oauth2` | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details!| -| `allowEmailRegister` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) | -| `allowGravatar` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance | -| `imageUploadType` | `imgur`, `s3`, `minio`, `azure` or `filesystem`(default) | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md)| -| `minio` | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also checkout our [Minio Image Upload Guide](docs/guides/minio-image-upload.md) | -| `s3` | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageuploadtype` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) | -| `s3bucket` | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` or `minio` | -| `sourceURL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | - -1: relative paths are based on CodiMD's base directory - -## Third-party integration API key settings - -| service | settings location | description | -| ------- | --------- | ----------- | -| facebook, twitter, github, gitlab, mattermost, dropbox, google, ldap, saml | environment variables or `config.json` | for signin | -| imgur, s3, minio, azure | environment variables or `config.json` | for image upload | -| dropbox(`dropbox/appKey`) | `config.json` | for export and import | - -## Third-party integration OAuth callback URLs - -| service | callback URL (after the server URL) | -| ------- | --------- | -| facebook | `/auth/facebook/callback` | -| twitter | `/auth/twitter/callback` | -| github | `/auth/github/callback` | -| gitlab | `/auth/gitlab/callback` | -| mattermost | `/auth/mattermost/callback` | -| dropbox | `/auth/dropbox/callback` | -| google | `/auth/google/callback` | -| saml | `/auth/saml/callback` | - -# Developer Notes - -## Structure - -```text -codimd/ -├── tmp/ --- temporary files -├── docs/ --- document files -├── lib/ --- server libraries -└── public/ --- client files - ├── css/ --- css styles - ├── js/ --- js scripts - ├── vendor/ --- vendor includes - └── views/ --- view templates -``` - -## Operational Transformation - -From 0.3.2, we started supporting operational transformation. -It makes concurrent editing safe and will not break up other users' operations. -Additionally, now can show other clients' selections. -See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/) - - # License -**License under AGPL.** +Licensed under AGPLv3. For our list of contributors, see [AUTHORS](AUTHORS). [matrix.org-image]: https://img.shields.io/badge/Matrix.org-%23CodiMD@matrix.org-green.svg [matrix.org-url]: https://riot.im/app/#/room/#codimd:matrix.org @@ -380,5 +80,6 @@ See more at [http://operational-transformation.github.io/](http://operational-tr [github-version-badge]: https://img.shields.io/github/release/codimd/server.svg [github-release-page]: https://github.com/codimd/server/releases [github-release-feed]: https://github.com/codimd/server/releases.atom +[github-issue-tracker]: https://github.com/codimd/server/issues/ [poeditor-image]: https://img.shields.io/badge/POEditor-translate-blue.svg [poeditor-url]: https://poeditor.com/join/project/1OpGjF2Jir diff --git a/docs/configuration-config-file.md b/docs/configuration-config-file.md new file mode 100644 index 0000000..4edbbf0 --- /dev/null +++ b/docs/configuration-config-file.md @@ -0,0 +1,64 @@ +# Configuration Using Config file + +You can choose to configure CodiMD with either a config file or with +[environment variables](configuration-env-vars.md). The config file is processed +in [`lib/config/index.js`](lib/config/index.js) - so this is the first +place to look if anything is missing not obvious from this document. + +Environment variables take precedence over configurations from the config files. +To get started, it is a good idea to take the `config.json.example` and copy it +to `config.json` before filling in your own details. + +[//]: # (TODO split up into chunks) + + +| variables | example values | description | +| --------- | ------ | ----------- | +| `debug` | `true` or `false` | set debug mode, show more logs | +| `domain` | `localhost` | domain name | +| `urlPath` | `codimd` | sub URL path, like `www.example.com/` | +| `host` | `localhost` | host to listen on | +| `port` | `80` | web app port | +| `path` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `host` and `port` are ignored) | +| `loglevel` | `info` | Defines what kind of logs are provided to stdout. | +| `allowOrigin` | `['localhost']` | domain name whitelist | +| `useSSL` | `true` or `false` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) | +| `hsts` | `{"enable": true, "maxAgeSeconds": 31536000, "includeSubdomains": true, "preload": true}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) | +| `csp` | `{"enable": true, "directives": {"scriptSrc": "trustworthy-scripts.example.com"}, "upgradeInsecureRequests": "auto", "addDefaults": true}` | Configures [Content Security Policy](https://helmetjs.github.io/docs/csp/). Directives are passed to Helmet - see [their documentation](https://helmetjs.github.io/docs/csp/) for more information on the format. Some defaults are added to the configured values so that the application doesn't break. To disable this behaviour, set `addDefaults` to `false`. Further, if `usecdn` is on, some CDN locations are allowed too. By default (`auto`), insecure (HTTP) requests are upgraded to HTTPS via CSP if `useSSL` is on. To change this behaviour, set `upgradeInsecureRequests` to either `true` or `false`. | +| `protocolUseSSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) | +| `urlAddPort` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) | +| `useCDN` | `true` or `false` | set to use CDN resources or not (default is `true`) | +| `allowAnonymous` | `true` or `false` | set to allow anonymous usage (default is `true`) | +| `allowAnonymousEdits` | `true` or `false` | if `allowAnonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) | +| `allowFreeURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL | +| `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` | +| `defaultPermission` | `freely`, `editable`, `limited`, `locked`, `protected` or `private` | set notes default permission (only applied on signed users) | +| `dbURL` | `mysql://localhost:3306/database` | set the db URL; if set, then db config (below) won't be applied | +| `db` | `{ "dialect": "sqlite", "storage": "./db.codimd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) | +| `sslKeyPath` | `./cert/client.key` | SSL key path1 (only need when you set `useSSL`) | +| `sslCertPath` | `./cert/codimd_io.crt` | SSL cert path1 (only need when you set `useSSL`) | +| `sslCAPath` | `['./cert/COMODORSAAddTrustCA.crt']` | SSL ca chain1 (only need when you set `useSSL`) | +| `dhParamPath` | `./cert/dhparam.pem` | SSL dhparam path1 (only need when you set `useSSL`) | +| `tmpPath` | `./tmp/` | temp directory path1 | +| `defaultNotePath` | `./public/default.md` | default note file path1 | +| `docsPath` | `./public/docs` | docs directory path1 | +| `viewPath` | `./public/views` | template directory path1 | +| `uploadsPath` | `./public/uploads` | uploads directory1 - needs to be persistent when you use imageUploadType `filesystem` | +| `sessionName` | `connect.sid` | cookie session name | +| `sessionSecret` | `secret` | cookie session secret | +| `sessionLife` | `14 * 24 * 60 * 60 * 1000` | cookie session life | +| `staticCacheTime` | `1 * 24 * 60 * 60 * 1000` | static file cache time | +| `heartbeatInterval` | `5000` | socket.io heartbeat interval | +| `heartbeatTimeout` | `10000` | socket.io heartbeat timeout | +| `documentMaxLength` | `100000` | note max length | +| `email` | `true` or `false` | set to allow email signin | +| `oauth2` | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details!| +| `allowEmailRegister` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) | +| `allowGravatar` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance | +| `imageUploadType` | `imgur`, `s3`, `minio`, `azure` or `filesystem`(default) | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md)| +| `minio` | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also checkout our [Minio Image Upload Guide](docs/guides/minio-image-upload.md) | +| `s3` | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageuploadtype` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) | +| `s3bucket` | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` or `minio` | +| `sourceURL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | + +1: relative paths are based on CodiMD's base directory diff --git a/docs/configuration-env-vars.md b/docs/configuration-env-vars.md new file mode 100644 index 0000000..2823a40 --- /dev/null +++ b/docs/configuration-env-vars.md @@ -0,0 +1,113 @@ +# Configuration Using Environment variables + +You can choose to configure CodiMD with either a +[config file](configuration-config-file.md) or with environment variables. +Environment variables are processed in +[`lib/config/environment.js`](lib/config/environment.js) - so this is the first +place to look if anything is missing not obvious from this document. + +Environment variables take precedence over configurations from the config files. +They generally start with `CMD_` for our own options, but we also list +node-specific options you can configure this way. + +[//]: # (TODO split up into chunks) + + +| variable | example value | description | default | +| -------- | ------------- | ----------- | ------- | +| `NODE_ENV` | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) | +| `DEBUG` | `true` or `false` | set debug mode; show more logs | +| `CMD_CONFIG_FILE` | `/path/to/config.json` | optional override for the path to CodiMD's config file | +| `CMD_DOMAIN` | `codimd.org` | domain name | +| `CMD_URL_PATH` | `codimd` | sub URL path, like `www.example.com/` | +| `CMD_HOST` | `localhost` | host to listen on | +| `CMD_PORT` | `80` | web app port | +| `CMD_PATH` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `CMD_HOST` and `CMD_PORT` are ignored) | +| `CMD_LOGLEVEL` | `info` | Defines what kind of logs are provided to stdout. | +| `CMD_ALLOW_ORIGIN` | `localhost, codimd.org` | domain name whitelist (use comma to separate) | +| `CMD_PROTOCOL_USESSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) | +| `CMD_URL_ADDPORT` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) | +| `CMD_USECDN` | `true` or `false` | set to use CDN resources or not (default is `true`) | +| `CMD_ALLOW_ANONYMOUS` | `true` or `false` | set to allow anonymous usage (default is `true`) | +| `CMD_ALLOW_ANONYMOUS_EDITS` | `true` or `false` | if `allowAnonymous` is `true`, allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) | +| `CMD_ALLOW_FREEURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL | +| `CMD_FORBIDDEN_NODE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` | +| `CMD_DEFAULT_PERMISSION` | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) | +| `CMD_DB_URL` | `mysql://localhost:3306/database` | set the database URL | +| `CMD_SESSION_SECRET` | no example | Secret used to sign the session cookie. If non is set, one will randomly generated on startup | +| `CMD_SESSION_LIFE` | `1209600000` | Session life time. (milliseconds) | +| `CMD_FACEBOOK_CLIENTID` | no example | Facebook API client id | +| `CMD_FACEBOOK_CLIENTSECRET` | no example | Facebook API client secret | +| `CMD_TWITTER_CONSUMERKEY` | no example | Twitter API consumer key | +| `CMD_TWITTER_CONSUMERSECRET` | no example | Twitter API consumer secret | +| `CMD_GITHUB_CLIENTID` | no example | GitHub API client id | +| `CMD_GITHUB_CLIENTSECRET` | no example | GitHub API client secret | +| `CMD_GITLAB_SCOPE` | `read_user` or `api` | GitLab API requested scope (default is `api`) (GitLab snippet import/export need `api` scope) | +| `CMD_GITLAB_BASEURL` | no example | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) | +| `CMD_GITLAB_CLIENTID` | no example | GitLab API client id | +| `CMD_GITLAB_CLIENTSECRET` | no example | GitLab API client secret | +| `CMD_GITLAB_VERSION` | no example | GitLab API version (v3 or v4) | +| `CMD_MATTERMOST_BASEURL` | no example | Mattermost authentication endpoint for versions below 5.0. For Mattermost version 5.0 and above, see [guide](docs/guides/auth/mattermost-self-hosted.md). | +| `CMD_MATTERMOST_CLIENTID` | no example | Mattermost API client id | +| `CMD_MATTERMOST_CLIENTSECRET` | no example | Mattermost API client secret | +| `CMD_DROPBOX_CLIENTID` | no example | Dropbox API client id | +| `CMD_DROPBOX_CLIENTSECRET` | no example | Dropbox API client secret | +| `CMD_GOOGLE_CLIENTID` | no example | Google API client id | +| `CMD_GOOGLE_CLIENTSECRET` | no example | Google API client secret | +| `CMD_LDAP_URL` | `ldap://example.com` | URL of LDAP server | +| `CMD_LDAP_BINDDN` | no example | bindDn for LDAP access | +| `CMD_LDAP_BINDCREDENTIALS` | no example | bindCredentials for LDAP access | +| `CMD_LDAP_SEARCHBASE` | `o=users,dc=example,dc=com` | LDAP directory to begin search from | +| `CMD_LDAP_SEARCHFILTER` | `(uid={{username}})` | LDAP filter to search with | +| `CMD_LDAP_SEARCHATTRIBUTES` | `displayName, mail` | LDAP attributes to search with (use comma to separate) | +| `CMD_LDAP_USERIDFIELD` | `uidNumber` or `uid` or `sAMAccountName` | The LDAP field which is used uniquely identify a user on CodiMD | +| `CMD_LDAP_USERNAMEFIELD` | Fallback to userid | The LDAP field which is used as the username on CodiMD | +| `CMD_LDAP_TLS_CA` | `server-cert.pem, root.pem` | Root CA for LDAP TLS in PEM format (use comma to separate) | +| `CMD_LDAP_PROVIDERNAME` | `My institution` | Optional name to be displayed at login form indicating the LDAP provider | +| `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](docs/guides/auth/saml-onelogin.md). | +| `CMD_SAML_IDPCERT` | `/path/to/cert.pem` | certificate file path of IdP in PEM format | +| `CMD_SAML_ISSUER` | no example | identity of the service provider (optional, default: serverurl)" | +| `CMD_SAML_IDENTIFIERFORMAT` | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) | +| `CMD_SAML_GROUPATTRIBUTE` | `memberOf` | attribute name for group list (optional) | +| `CMD_SAML_REQUIREDGROUPS` | `Hackmd-users` | group names that allowed (use vertical bar to separate) (optional) | +| `CMD_SAML_EXTERNALGROUPS` | `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) | +| `CMD_SAML_ATTRIBUTE_ID` | `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) | +| `CMD_SAML_ATTRIBUTE_USERNAME` | `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) | +| `CMD_SAML_ATTRIBUTE_EMAIL` | `mail` | attribute map for `email` (optional, default: NameID of SAML response if `CMD_SAML_IDENTIFIERFORMAT` is default) | +| `CMD_OAUTH2_USER_PROFILE_URL` | `https://example.com` | where retrieve information about a user after succesful login. Needs to output JSON. (no default value) Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details on all of the `CMD_OAUTH2...` options. | +| `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR` | `name` | where to find the username in the JSON from the user profile URL. (no default value)| +| `CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR` | `display-name` | where to find the display-name in the JSON from the user profile URL. (no default value) | +| `CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR` | `email` | where to find the email address in the JSON from the user profile URL. (no default value) | +| `CMD_OAUTH2_TOKEN_URL` | `https://example.com` | sometimes called token endpoint, please refer to the documentation of your OAuth2 provider (no default value) | +| `CMD_OAUTH2_AUTHORIZATION_URL` | `https://example.com` | authorization URL of your provider, please refer to the documentation of your OAuth2 provider (no default value) | +| `CMD_OAUTH2_CLIENT_ID` | `afae02fckafd...` | you will get this from your OAuth2 provider when you register CodiMD as OAuth2-client, (no default value) | +| `CMD_OAUTH2_CLIENT_SECRET` | `afae02fckafd...` | you will get this from your OAuth2 provider when you register CodiMD as OAuth2-client, (no default value) | +| `CMD_OAUTH2_PROVIDERNAME` | `My institution` | Optional name to be displayed at login form indicating the oAuth2 provider | +| `CMD_IMGUR_CLIENTID` | no example | Imgur API client id | +| `CMD_EMAIL` | `true` or `false` | set to allow email signin | +| `CMD_ALLOW_PDF_EXPORT` | `true` or `false` | Enable or disable PDF exports | +| `CMD_ALLOW_EMAIL_REGISTER` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) | +| `CMD_ALLOW_GRAVATAR` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance | +| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio` or `filesystem` | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md) | +| `CMD_S3_ACCESS_KEY_ID` | no example | AWS access key id | +| `CMD_S3_SECRET_ACCESS_KEY` | no example | AWS secret key | +| `CMD_S3_REGION` | `ap-northeast-1` | AWS S3 region | +| `CMD_S3_BUCKET` | no example | AWS S3 bucket name | +| `CMD_MINIO_ACCESS_KEY` | no example | Minio access key | +| `CMD_MINIO_SECRET_KEY` | no example | Minio secret key | +| `CMD_MINIO_ENDPOINT` | `minio.example.org` | Address of your Minio endpoint/instance | +| `CMD_MINIO_PORT` | `9000` | Port that is used for your Minio instance | +| `CMD_MINIO_SECURE` | `true` | If set to `true` HTTPS is used for Minio | +| `CMD_AZURE_CONNECTION_STRING` | no example | Azure Blob Storage connection string | +| `CMD_AZURE_CONTAINER` | no example | Azure Blob Storage container name (automatically created if non existent) | +| `CMD_HSTS_ENABLE` | ` true` | set to enable [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) if HTTPS is also enabled (default is ` true`) | +| `CMD_HSTS_INCLUDE_SUBDOMAINS` | `true` | set to include subdomains in HSTS (default is `true`) | +| `CMD_HSTS_MAX_AGE` | `31536000` | max duration in seconds to tell clients to keep HSTS status (default is a year) | +| `CMD_HSTS_PRELOAD` | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) | +| `CMD_CSP_ENABLE` | `true` | whether to enable Content Security Policy (directives cannot be configured with environment variables) | +| `CMD_CSP_REPORTURI` | `https://.report-uri.com/r/d/csp/enforce` | Allows to add a URL for CSP reports in case of violations | +| `CMD_SOURCE_URL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | + +**Note:** *Due to the rename process we renamed all `HMD_`-prefix variables to be `CMD_`-prefixed. The old ones continue to work.* + +**Note:** *relative paths are based on CodiMD's base directory* diff --git a/docs/dev/getting-started.md b/docs/dev/getting-started.md new file mode 100644 index 0000000..6eb1b13 --- /dev/null +++ b/docs/dev/getting-started.md @@ -0,0 +1,16 @@ +# Developer Notes + +## Structure + +```text +codimd/ +├── test/ --- test suite +├── docs/ --- documentation +├── lib/ --- server libraries +└── public/ --- client files + ├── css/ --- css styles + ├── docs/ --- default documents + ├── js/ --- js scripts + ├── vendor/ --- vendor includes + └── views/ --- view templates +``` diff --git a/docs/dev/ot.md b/docs/dev/ot.md new file mode 100644 index 0000000..71f7f78 --- /dev/null +++ b/docs/dev/ot.md @@ -0,0 +1,6 @@ +## Operational Transformation + +From 0.3.2, we started supporting operational transformation. +It makes concurrent editing safe and will not break up other users' operations. +Additionally, now can show other clients' selections. +See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/) diff --git a/docs/guides/auth/github.md b/docs/guides/auth/github.md index d6a1095..482bca3 100644 --- a/docs/guides/auth/github.md +++ b/docs/guides/auth/github.md @@ -7,17 +7,17 @@ Authentication guide - GitHub 2. Navigate to developer settings in your GitHub account [here](https://github.com/settings/developers) and select the "OAuth Apps" tab 3. Click on the **New OAuth App** button, to create a new OAuth App: -![create-oauth-app](../images/auth/create-oauth-app.png) +![create-oauth-app](../../images/auth/create-oauth-app.png) 4. Fill out the new OAuth application registration form, and click **Register Application** -![register-oauth-application-form](../images/auth/register-oauth-application-form.png) +![register-oauth-application-form](../../images/auth/register-oauth-application-form.png) *Note: The callback URL is /auth/github/callback* 5. After successfully registering the application, you'll receive the Client ID and Client Secret for the application -![application-page](../images/auth/application-page.png) +![application-page](../../images/auth/application-page.png) 6. Add the Client ID and Client Secret to your config.json file or pass them as environment variables * config.json: diff --git a/docs/guides/auth/gitlab-self-hosted.md b/docs/guides/auth/gitlab-self-hosted.md index 60f6261..d19caf7 100644 --- a/docs/guides/auth/gitlab-self-hosted.md +++ b/docs/guides/auth/gitlab-self-hosted.md @@ -7,12 +7,12 @@ 2. Navigate to the application management page at `https://your.gitlab.domain/admin/applications` (admin permissions required) 3. Click **New application** to create a new application and fill out the registration form: -![New GitLab application](../images/auth/gitlab-new-application.png) +![New GitLab application](../../images/auth/gitlab-new-application.png) 4. Click **Submit** 5. In the list of applications select **HackMD**. Leave that site open to copy the application ID and secret in the next step. -![Application: HackMD](../images/auth/gitlab-application-details.png) +![Application: HackMD](../../images/auth/gitlab-application-details.png) 6. In the `docker-compose.yml` add the following environment variables to `app:` `environment:` @@ -29,4 +29,4 @@ 7. Run `docker-compose up -d` to apply your settings. 8. Sign in to your HackMD using your GitLab ID: -![Sign in via GitLab](../images/auth/gitlab-sign-in.png) +![Sign in via GitLab](../../images/auth/gitlab-sign-in.png) diff --git a/docs/guides/auth/mattermost-self-hosted.md b/docs/guides/auth/mattermost-self-hosted.md index 631aabd..e305059 100644 --- a/docs/guides/auth/mattermost-self-hosted.md +++ b/docs/guides/auth/mattermost-self-hosted.md @@ -8,22 +8,22 @@ This guide uses the generic OAuth2 module for compatibility with Mattermost vers 1. Sign-in with an administrator account to your Mattermost instance 2. Make sure **OAuth 2.0 Service Provider** is enabled in the Main Menu (menu button next to your username in the top left corner) --> System Console --> Custom Integrations menu, which you can find at `https://your.mattermost.domain/admin_console/integrations/custom` -![mattermost-enable-oauth2](../images/auth/mattermost-enable-oauth2.png) +![mattermost-enable-oauth2](../../images/auth/mattermost-enable-oauth2.png) 3. Navigate to the OAuth integration settings through Main Menu --> Integrations --> OAuth 2.0 Applications, at `https://your.mattermost.domain/yourteam/integrations/oauth2-apps` 4. Click on the **Add OAuth 2.0 Application** button to add a new OAuth application -![mattermost-oauth-app-add](../images/auth/mattermost-oauth-app-add.png) +![mattermost-oauth-app-add](../../images/auth/mattermost-oauth-app-add.png) 5. Fill out the form and click **Save** -![mattermost-oauth-app-form](../images/auth/mattermost-oauth-app-form.png) +![mattermost-oauth-app-form](../../images/auth/mattermost-oauth-app-form.png) *Note: The callback URL is \/auth/oauth2/callback* 6. After saving the application, you'll receive the Client ID and Client Secret -![mattermost-oauth-app-done](../images/auth/mattermost-oauth-app-done.png) +![mattermost-oauth-app-done](../../images/auth/mattermost-oauth-app-done.png) 7. Add the Client ID and Client Secret to your config.json file or pass them as environment variables * config.json: diff --git a/docs/guides/auth/nextcloud.md b/docs/guides/auth/nextcloud.md index 108772d..42db8b4 100644 --- a/docs/guides/auth/nextcloud.md +++ b/docs/guides/auth/nextcloud.md @@ -10,14 +10,14 @@ This guide uses the generic OAuth2 module for compatibility with Nextcloud 13 an 2. Navigate to the OAuth integration settings: Profile Icon (top right) --> Settings Then choose Security Settings from the *Administration* part of the list - Don't confuse this with Personal Security Settings, where you would change your personal password! At the top there's OAuth 2.0-Clients. - ![Where to find OAuth2 in Nextcloud](../images/auth/nextcloud-oauth2-1-settings.png) + ![Where to find OAuth2 in Nextcloud](../../images/auth/nextcloud-oauth2-1-settings.png) 3. Add your CodiMD instance by giving it a *name* (perhaps CodiMD, but could be anything) and a *Redirection-URI*. The Redirection-URI will be `\/auth/oauth2/callback`. Click Add. - ![Adding a client to Nextcloud](../images/auth/nextcloud-oauth2-2-client-add.png) + ![Adding a client to Nextcloud](../../images/auth/nextcloud-oauth2-2-client-add.png) 4. You'll now see a line containing a *client identifier* and a *Secret*. - ![Successfully added OAuth2-client](../images/auth/nextcloud-oauth2-3-clientid-secret.png) + ![Successfully added OAuth2-client](../../images/auth/nextcloud-oauth2-3-clientid-secret.png) 5. That's it for Nextcloud, the rest is configured in your CodiMD `config.json` or via the `CMD_` environment variables! diff --git a/docs/guides/auth/oauth.md b/docs/guides/auth/oauth.md new file mode 100644 index 0000000..46314e2 --- /dev/null +++ b/docs/guides/auth/oauth.md @@ -0,0 +1,12 @@ +# OAuth general information + +| service | callback URL (after the server URL) | +| ------- | --------- | +| facebook | `/auth/facebook/callback` | +| twitter | `/auth/twitter/callback` | +| github | `/auth/github/callback` | +| gitlab | `/auth/gitlab/callback` | +| mattermost | `/auth/mattermost/callback` | +| dropbox | `/auth/dropbox/callback` | +| google | `/auth/google/callback` | +| saml | `/auth/saml/callback` | diff --git a/docs/guides/auth/saml-onelogin.md b/docs/guides/auth/saml-onelogin.md index 02a5ffa..14ce61a 100644 --- a/docs/guides/auth/saml-onelogin.md +++ b/docs/guides/auth/saml-onelogin.md @@ -7,15 +7,15 @@ Authentication guide - SAML (OneLogin) 2. Go to the administration page. 3. Select the **APPS** menu and click on the **Add Apps**. -![onelogin-add-app](../images/auth/onelogin-add-app.png) +![onelogin-add-app](../../images/auth/onelogin-add-app.png) 4. Find "SAML Test Connector (SP)" for template of settings and select it. -![onelogin-select-template](../images/auth/onelogin-select-template.png) +![onelogin-select-template](../../images/auth/onelogin-select-template.png) 5. Edit display name and icons for OneLogin dashboard as you want, and click **SAVE**. -![onelogin-edit-app-name](../images/auth/onelogin-edit-app-name.png) +![onelogin-edit-app-name](../../images/auth/onelogin-edit-app-name.png) 6. After that other tabs will appear, click the **Configuration**, and fill out the below items, and click **SAVE**. * RelayState: The base URL of your hackmd, which is issuer. (last slash is not needed) @@ -23,13 +23,13 @@ Authentication guide - SAML (OneLogin) * ACS (Consumer) URL: same as above. * Login URL: login URL(SAML requester) of your hackmd. (serverurl + /auth/saml) -![onelogin-edit-sp-metadata](../images/auth/onelogin-edit-sp-metadata.png) +![onelogin-edit-sp-metadata](../../images/auth/onelogin-edit-sp-metadata.png) 7. The registration is completed. Next, click **SSO** and copy or download the items below. * X.509 Certificate: Click **View Details** and **DOWNLOAD** or copy the content of certificate ....(A) * SAML 2.0 Endpoint (HTTP): Copy the URL ....(B) -![onelogin-copy-idp-metadata](../images/auth/onelogin-copy-idp-metadata.png) +![onelogin-copy-idp-metadata](../../images/auth/onelogin-copy-idp-metadata.png) 8. In your hackmd server, create IdP certificate file from (A) 9. Add the IdP URL (B) and the Idp certificate file path to your config.json file or pass them as environment variables. @@ -51,4 +51,4 @@ Authentication guide - SAML (OneLogin) ```` 10. Try sign-in with SAML from your hackmd sign-in button or OneLogin dashboard (like the screenshot below). -![onelogin-use-dashboard](../images/auth/onelogin-use-dashboard.png) +![onelogin-use-dashboard](../../images/auth/onelogin-use-dashboard.png) diff --git a/docs/guides/auth/twitter.md b/docs/guides/auth/twitter.md index 1b96288..da35a4e 100644 --- a/docs/guides/auth/twitter.md +++ b/docs/guides/auth/twitter.md @@ -7,11 +7,11 @@ Authentication guide - Twitter 2. Go to the Twitter Application management page [here](https://apps.twitter.com/) 3. Click on the **Create New App** button to create a new Twitter app: -![create-twitter-app](../images/auth/create-twitter-app.png) +![create-twitter-app](../../images/auth/create-twitter-app.png) 4. Fill out the create application form, check the developer agreement box, and click **Create Your Twitter Application** -![register-twitter-application](../images/auth/register-twitter-application.png) +![register-twitter-application](../../images/auth/register-twitter-application.png) *Note: you may have to register your phone number with Twitter to create a Twitter application* @@ -19,11 +19,11 @@ To do this Click your profile icon --> Settings and privacy --> Mobile --> Sele 5. After you receive confirmation that the Twitter application was created, click **Keys and Access Tokens** -![twitter-app-confirmation](../images/auth/twitter-app-confirmation.png) +![twitter-app-confirmation](../../images/auth/twitter-app-confirmation.png) 6. Obtain your Twitter Consumer Key and Consumer Secret -![twitter-app-keys](../images/auth/twitter-app-keys.png) +![twitter-app-keys](../../images/auth/twitter-app-keys.png) 7. Add your Consumer Key and Consumer Secret to your config.json file or pass them as environment variables: * config.json: diff --git a/docs/guides/migrations-and-breaking-changes.md b/docs/guides/migrations-and-breaking-changes.md new file mode 100644 index 0000000..f5a416f --- /dev/null +++ b/docs/guides/migrations-and-breaking-changes.md @@ -0,0 +1,58 @@ +# Migrations and Notable Changes + +## Migrating to 1.3.2 + +This is not a breaking change, but to stay up to date with the community +repository, you may need to update a few urls. This is not a breaking change. + +See more at [issue #10](https://github.com/codimd/server/issues/10) + +**Native setup using git:** + +Change the upstream remote using `git remote set-url origin https://github.com/codimd/server.git`. + +**Docker:** + +When you use our [container repository](https://github.com/codimd/container) +(which was previously `codimd-container`) all you can simply run `git pull` and +your `docker-compose.yml` will be updated. + +When you setup things yourself, make sure you use the new image: +[`quay.io/codimd/server`](https://quay.io/repository/codimd/server?tab=tags). + +**Heroku:** + +All you need to do is [disconnect GitHub](https://devcenter.heroku.com/articles/github-integration#disconnecting-from-github) +and [reconnect it](https://devcenter.heroku.com/articles/github-integration#enabling-github-integration) +with this new repository. + +Or you can use our Heroku button and redeploy your instance and link the old +database again. + +## Migrating to 1.1.0 + +We deprecated the older lower case config style and moved on to camel case style. Please have a look at the current `config.json.example` and check the warnings on startup. + +*Notice: This is not a breaking change right now but will be in the future* + +## Migrating to 0.5.0 + +[**migration-to-0.5.0**](https://github.com/hackmdio/migration-to-0.5.0) + +We don't use LZString to compress socket.io data and DB data after version 0.5.0. +Please run the migration tool if you're upgrading from the old version. + +## Migrating to 0.4.0 + +[**migration-to-0.4.0**](https://github.com/hackmdio/migration-to-0.4.0) + +We've dropped MongoDB after version 0.4.0. +So here is the migration tool for you to transfer the old DB data to the new DB. +This tool is also used for official service. + +## Operational Transformation in 0.3.2 + +From 0.3.2, we started supporting operational transformation. +It makes concurrent editing safe and will not break up other users' operations. +Additionally, now can show other clients' selections. +See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/) diff --git a/docs/guides/minio-image-upload.md b/docs/guides/minio-image-upload.md index 7f5796c..1544e24 100644 --- a/docs/guides/minio-image-upload.md +++ b/docs/guides/minio-image-upload.md @@ -22,22 +22,22 @@ Minio Guide for CodiMD docker logs test-minio ``` - ![docker logs](images/minio-image-upload/docker-logs.png) + ![docker logs](../images/minio-image-upload/docker-logs.png) 3. Open http://localhost:9000 and login with the shown credentials. - ![minio default view](images/minio-image-upload/default-view.png) + ![minio default view](../images/minio-image-upload/default-view.png) 4. Create a bucket for HackMD - ![minio create bucket](images/minio-image-upload/create-bucket.png) + ![minio create bucket](../images/minio-image-upload/create-bucket.png) 5. Add a policy for the prefix `uploads` and make it read-only. - ![minio edit policy](images/minio-image-upload/open-edit-policy.png) + ![minio edit policy](../images/minio-image-upload/open-edit-policy.png) *Open policy editor* - ![minio policy adding](images/minio-image-upload/create-policy.png) + ![minio policy adding](../images/minio-image-upload/create-policy.png) *Add policy for uploads* 6. Set credentials and configs for Minio in HackMD's `config.json` diff --git a/docs/guides/s3-image-upload.md b/docs/guides/s3-image-upload.md index 40ab868..2943701 100644 --- a/docs/guides/s3-image-upload.md +++ b/docs/guides/s3-image-upload.md @@ -4,15 +4,15 @@ 1. Go to [AWS S3 console](https://console.aws.amazon.com/s3/home) and create a new bucket. - ![create-bucket](images/s3-image-upload/create-bucket.png) + ![create-bucket](../images/s3-image-upload/create-bucket.png) 2. Click on bucket, select **Properties** on the side panel, and find **Permission** section. Click **Edit bucket policy**. - ![bucket-property](images/s3-image-upload/bucket-property.png) + ![bucket-property](../images/s3-image-upload/bucket-property.png) 3. Enter the following policy, replace `bucket_name` with your bucket name: - ![bucket-policy-editor](images/s3-image-upload/bucket-policy-editor.png) + ![bucket-policy-editor](../images/s3-image-upload/bucket-policy-editor.png) ```json { @@ -32,15 +32,15 @@ 5. Enter user page, select **Permission** tab, look at **Inline Policies** section, and click **Create User Policy** - ![iam-user](images/s3-image-upload/iam-user.png) + ![iam-user](../images/s3-image-upload/iam-user.png) 6. Select **Custom Policy** - ![custom-policy](images/s3-image-upload/custom-policy.png) + ![custom-policy](../images/s3-image-upload/custom-policy.png) 7. Enter the following policy, replace `bucket_name` with your bucket name: - ![review-policy](images/s3-image-upload/review-policy.png) + ![review-policy](../images/s3-image-upload/review-policy.png) ```json { diff --git a/docs/history.md b/docs/history.md new file mode 100644 index 0000000..b1dfde8 --- /dev/null +++ b/docs/history.md @@ -0,0 +1,39 @@ +# History of CodiMD + +## It started with HackMD + +HackMD is the origin of this project, which was mostly developed by Max Wu and +Yukai Huang. Originally, this was open source under MIT license, but was +[relicensed in October 2017 to be AGPLv3](https://github.com/hackmdio/codimd/pull/578). +At the same time, [hackmd.io](https://hackmd.io) was founded to offer a +commercial version of HackMD. + +The AGPLv3-version was developed and released by the community, this was for a +while referred to as "HackMD community edition". + +*For more on the splitting of the projects, please refer to [A note to our community (2017-10-11)](https://hackmd.io/c/community-news/https%3A%2F%2Fhackmd.io%2Fs%2Fr1_4j9_hZ).* + + +## HackMD CE became CodiMD + +In June 2018, CodiMD was renamed from its former name "HackMD" and continued to +be developed under AGPLv3 by the community. We decided to change the name to +break the confusion between HackMD (enterprise offering) and CodiMD (community +project), as people mistook it for an open core development model. + +*For the whole renaming story, see the [issue where the renaming was discussed](https://github.com/hackmdio/hackmd/issues/720).* + + +## CodiMD went independent + +In March 2019, a discussion over licensing, governance and the future of CodiMD +lead to the formation of a distinct GitHub organization. Up to that point, the +community project resided in the organization of hackmdio but was for the most +part self-organized. + +During that debate, we did not reach an agreement that would have allowed us to +move the repository, so we simply forked it. We still welcome the HackMD team +as part of our community, especially since a large portion of this code base +originated with them. + +*For the debate that lead to this step, please refer to the [governance debate](https://github.com/hackmdio/hackmd/issues/1170) and [the announcement of the new repository](https://github.com/codimd/server/issues/10).* diff --git a/docs/images/CodiMD-1.3.2-features.png b/docs/images/CodiMD-1.3.2-features.png new file mode 100644 index 0000000000000000000000000000000000000000..952efe304a91a927ab6a403964462b9215172d1c GIT binary patch literal 81971 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

!`X_}wDMdGH*o7=CBbWa| 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 D(Uuw! literal 0 HcmV?d00001 diff --git a/docs/guides/images/auth/application-page.png b/docs/images/auth/application-page.png similarity index 100% rename from docs/guides/images/auth/application-page.png rename to docs/images/auth/application-page.png diff --git a/docs/guides/images/auth/create-oauth-app.png b/docs/images/auth/create-oauth-app.png similarity index 100% rename from docs/guides/images/auth/create-oauth-app.png rename to docs/images/auth/create-oauth-app.png diff --git a/docs/guides/images/auth/create-twitter-app.png b/docs/images/auth/create-twitter-app.png similarity index 100% rename from docs/guides/images/auth/create-twitter-app.png rename to docs/images/auth/create-twitter-app.png diff --git a/docs/guides/images/auth/gitlab-application-details.png b/docs/images/auth/gitlab-application-details.png similarity index 100% rename from docs/guides/images/auth/gitlab-application-details.png rename to docs/images/auth/gitlab-application-details.png diff --git a/docs/guides/images/auth/gitlab-new-application.png b/docs/images/auth/gitlab-new-application.png similarity index 100% rename from docs/guides/images/auth/gitlab-new-application.png rename to docs/images/auth/gitlab-new-application.png diff --git a/docs/guides/images/auth/gitlab-sign-in.png b/docs/images/auth/gitlab-sign-in.png similarity index 100% rename from docs/guides/images/auth/gitlab-sign-in.png rename to docs/images/auth/gitlab-sign-in.png diff --git a/docs/guides/images/auth/mattermost-enable-oauth2.png b/docs/images/auth/mattermost-enable-oauth2.png similarity index 100% rename from docs/guides/images/auth/mattermost-enable-oauth2.png rename to docs/images/auth/mattermost-enable-oauth2.png diff --git a/docs/guides/images/auth/mattermost-oauth-app-add.png b/docs/images/auth/mattermost-oauth-app-add.png similarity index 100% rename from docs/guides/images/auth/mattermost-oauth-app-add.png rename to docs/images/auth/mattermost-oauth-app-add.png diff --git a/docs/guides/images/auth/mattermost-oauth-app-done.png b/docs/images/auth/mattermost-oauth-app-done.png similarity index 100% rename from docs/guides/images/auth/mattermost-oauth-app-done.png rename to docs/images/auth/mattermost-oauth-app-done.png diff --git a/docs/guides/images/auth/mattermost-oauth-app-form.png b/docs/images/auth/mattermost-oauth-app-form.png similarity index 100% rename from docs/guides/images/auth/mattermost-oauth-app-form.png rename to docs/images/auth/mattermost-oauth-app-form.png diff --git a/docs/guides/images/auth/nextcloud-oauth2-1-settings.png b/docs/images/auth/nextcloud-oauth2-1-settings.png similarity index 100% rename from docs/guides/images/auth/nextcloud-oauth2-1-settings.png rename to docs/images/auth/nextcloud-oauth2-1-settings.png diff --git a/docs/guides/images/auth/nextcloud-oauth2-2-client-add.png b/docs/images/auth/nextcloud-oauth2-2-client-add.png similarity index 100% rename from docs/guides/images/auth/nextcloud-oauth2-2-client-add.png rename to docs/images/auth/nextcloud-oauth2-2-client-add.png diff --git a/docs/guides/images/auth/nextcloud-oauth2-3-clientid-secret.png b/docs/images/auth/nextcloud-oauth2-3-clientid-secret.png similarity index 100% rename from docs/guides/images/auth/nextcloud-oauth2-3-clientid-secret.png rename to docs/images/auth/nextcloud-oauth2-3-clientid-secret.png diff --git a/docs/guides/images/auth/onelogin-add-app.png b/docs/images/auth/onelogin-add-app.png similarity index 100% rename from docs/guides/images/auth/onelogin-add-app.png rename to docs/images/auth/onelogin-add-app.png diff --git a/docs/guides/images/auth/onelogin-copy-idp-metadata.png b/docs/images/auth/onelogin-copy-idp-metadata.png similarity index 100% rename from docs/guides/images/auth/onelogin-copy-idp-metadata.png rename to docs/images/auth/onelogin-copy-idp-metadata.png diff --git a/docs/guides/images/auth/onelogin-edit-app-name.png b/docs/images/auth/onelogin-edit-app-name.png similarity index 100% rename from docs/guides/images/auth/onelogin-edit-app-name.png rename to docs/images/auth/onelogin-edit-app-name.png diff --git a/docs/guides/images/auth/onelogin-edit-sp-metadata.png b/docs/images/auth/onelogin-edit-sp-metadata.png similarity index 100% rename from docs/guides/images/auth/onelogin-edit-sp-metadata.png rename to docs/images/auth/onelogin-edit-sp-metadata.png diff --git a/docs/guides/images/auth/onelogin-select-template.png b/docs/images/auth/onelogin-select-template.png similarity index 100% rename from docs/guides/images/auth/onelogin-select-template.png rename to docs/images/auth/onelogin-select-template.png diff --git a/docs/guides/images/auth/onelogin-use-dashboard.png b/docs/images/auth/onelogin-use-dashboard.png similarity index 100% rename from docs/guides/images/auth/onelogin-use-dashboard.png rename to docs/images/auth/onelogin-use-dashboard.png diff --git a/docs/guides/images/auth/register-oauth-application-form.png b/docs/images/auth/register-oauth-application-form.png similarity index 100% rename from docs/guides/images/auth/register-oauth-application-form.png rename to docs/images/auth/register-oauth-application-form.png diff --git a/docs/guides/images/auth/register-twitter-application.png b/docs/images/auth/register-twitter-application.png similarity index 100% rename from docs/guides/images/auth/register-twitter-application.png rename to docs/images/auth/register-twitter-application.png diff --git a/docs/guides/images/auth/twitter-app-confirmation.png b/docs/images/auth/twitter-app-confirmation.png similarity index 100% rename from docs/guides/images/auth/twitter-app-confirmation.png rename to docs/images/auth/twitter-app-confirmation.png diff --git a/docs/guides/images/auth/twitter-app-keys.png b/docs/images/auth/twitter-app-keys.png similarity index 100% rename from docs/guides/images/auth/twitter-app-keys.png rename to docs/images/auth/twitter-app-keys.png diff --git a/docs/guides/images/minio-image-upload/create-bucket.png b/docs/images/minio-image-upload/create-bucket.png similarity index 100% rename from docs/guides/images/minio-image-upload/create-bucket.png rename to docs/images/minio-image-upload/create-bucket.png diff --git a/docs/guides/images/minio-image-upload/create-policy.png b/docs/images/minio-image-upload/create-policy.png similarity index 100% rename from docs/guides/images/minio-image-upload/create-policy.png rename to docs/images/minio-image-upload/create-policy.png diff --git a/docs/guides/images/minio-image-upload/default-view.png b/docs/images/minio-image-upload/default-view.png similarity index 100% rename from docs/guides/images/minio-image-upload/default-view.png rename to docs/images/minio-image-upload/default-view.png diff --git a/docs/guides/images/minio-image-upload/docker-logs.png b/docs/images/minio-image-upload/docker-logs.png similarity index 100% rename from docs/guides/images/minio-image-upload/docker-logs.png rename to docs/images/minio-image-upload/docker-logs.png diff --git a/docs/guides/images/minio-image-upload/open-edit-policy.png b/docs/images/minio-image-upload/open-edit-policy.png similarity index 100% rename from docs/guides/images/minio-image-upload/open-edit-policy.png rename to docs/images/minio-image-upload/open-edit-policy.png diff --git a/docs/guides/images/s3-image-upload/bucket-policy-editor.png b/docs/images/s3-image-upload/bucket-policy-editor.png similarity index 100% rename from docs/guides/images/s3-image-upload/bucket-policy-editor.png rename to docs/images/s3-image-upload/bucket-policy-editor.png diff --git a/docs/guides/images/s3-image-upload/bucket-property.png b/docs/images/s3-image-upload/bucket-property.png similarity index 100% rename from docs/guides/images/s3-image-upload/bucket-property.png rename to docs/images/s3-image-upload/bucket-property.png diff --git a/docs/guides/images/s3-image-upload/create-bucket.png b/docs/images/s3-image-upload/create-bucket.png similarity index 100% rename from docs/guides/images/s3-image-upload/create-bucket.png rename to docs/images/s3-image-upload/create-bucket.png diff --git a/docs/guides/images/s3-image-upload/custom-policy.png b/docs/images/s3-image-upload/custom-policy.png similarity index 100% rename from docs/guides/images/s3-image-upload/custom-policy.png rename to docs/images/s3-image-upload/custom-policy.png diff --git a/docs/guides/images/s3-image-upload/iam-user.png b/docs/images/s3-image-upload/iam-user.png similarity index 100% rename from docs/guides/images/s3-image-upload/iam-user.png rename to docs/images/s3-image-upload/iam-user.png diff --git a/docs/guides/images/s3-image-upload/review-policy.png b/docs/images/s3-image-upload/review-policy.png similarity index 100% rename from docs/guides/images/s3-image-upload/review-policy.png rename to docs/images/s3-image-upload/review-policy.png diff --git a/docs/setup/cloudron.md b/docs/setup/cloudron.md new file mode 100644 index 0000000..edab7d0 --- /dev/null +++ b/docs/setup/cloudron.md @@ -0,0 +1,5 @@ +## Cloudron + +Install CodiMD on [Cloudron](https://cloudron.io): + +[![Install](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=io.hackmd.cloudronapp) diff --git a/docs/setup/docker.md b/docs/setup/docker.md new file mode 100644 index 0000000..defe467 --- /dev/null +++ b/docs/setup/docker.md @@ -0,0 +1,22 @@ + +## CodiMD by docker container +[![Try in PWD](https://cdn.rawgit.com/play-with-docker/stacks/cff22438/assets/images/button.png)](http://play-with-docker.com?stack=https://github.com/codimd/container/raw/master/docker-compose.yml&stack_name=codimd) + + +**Debian-based version:** + +[![Docker Repository on Quay](https://quay.io/repository/codimd/server/status "Docker Repository on Quay")](https://quay.io/repository/codimd/server) + + +**Alpine-based version:** + +[![Docker Repository on Quay](https://quay.io/repository/codimd/server/status "Docker Repository on Quay")](https://quay.io/repository/codimd/server) + +The easiest way to setup CodiMD using docker are using the following three commands: + +```console +git clone https://github.com/codimd/container.git +cd codimd-container +docker-compose up +``` +Read more about it in the [container repository…](https://github.com/codimd/container) diff --git a/docs/setup/heroku.md b/docs/setup/heroku.md new file mode 100644 index 0000000..86b4eca --- /dev/null +++ b/docs/setup/heroku.md @@ -0,0 +1,6 @@ +## Heroku Deployment + +You can quickly setup a sample Heroku CodiMD application by clicking the button +below. + +[![Deploy on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/codimd/server/tree/master) diff --git a/docs/setup/kubernetes.md b/docs/setup/kubernetes.md new file mode 100644 index 0000000..400e2b4 --- /dev/null +++ b/docs/setup/kubernetes.md @@ -0,0 +1,5 @@ +## Kubernetes + +To install use `helm install stable/hackmd`. + +For all further details, please check out the offical CodiMD [K8s helm chart](https://github.com/kubernetes/charts/tree/master/stable/hackmd). diff --git a/docs/setup/manual-setup.md b/docs/setup/manual-setup.md new file mode 100644 index 0000000..25869ee --- /dev/null +++ b/docs/setup/manual-setup.md @@ -0,0 +1,37 @@ +# Manual Installation + +## Requirements on your server + +- Node.js 6.x or up (test up to 7.5.0) and <10.x +- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8` +- npm (and its dependencies, [node-gyp](https://github.com/nodejs/node-gyp#installation)) +- `libssl-dev` for building scrypt (see [here](https://github.com/ml1nk/node-scrypt/blob/master/README.md#installation-instructions) for further information) +- For **building** CodiMD we recommend to use a machine with at least **2GB** RAM + +## Instructions + +1. Download a release and unzip or clone into a directory +2. Enter the directory and type `bin/setup`, which will install npm dependencies and create configs. The setup script is written in Bash, you would need bash as a prerequisite. +3. Setup the configs, see more below +4. Setup environment variables which will overwrite the configs +5. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development) +6. Modify the file named `.sequelizerc`, change the value of the variable `url` with your db connection string + For example: `postgres://username:password@localhost:5432/codimd` +7. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema +8. Run the server as you like (node, forever, pm2) + + +## How to upgrade your installation + +:warning: When you are still running from the old repository, please run: `git remote set-url origin https://github.com/codimd/server.git` :warning: + +If you are upgrading CodiMD from an older version, follow these steps: + +1. Fully stop your old server first (important) +2. `git pull` or do whatever that updates the files +3. `npm install` to update dependencies +4. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development) +5. Modify the file named `.sequelizerc`, change the value of the variable `url` with your db connection string + For example: `postgres://username:password@localhost:5432/codimd` +6. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema +7. Start your whole new server! From fb973d2a6f4b9873bd89cf0e3dcffa2472bdec39 Mon Sep 17 00:00:00 2001 From: Claudius Date: Sun, 31 Mar 2019 16:19:19 +0200 Subject: [PATCH 15/36] removing doctoc, which is no longer being used Signed-off-by: Claudius --- .travis.yml | 7 ------- package.json | 4 +--- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 52da97f..37e3352 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,13 +30,6 @@ jobs: script: - shellcheck bin/heroku bin/setup language: generic - - env: task=doctoc - install: npm install doctoc - script: - - cp README.md README.md.orig - - npm run doctoc - - diff -q README.md README.md.orig - language: generic - env: task=json-lint addons: apt: diff --git a/package.json b/package.json index c17c0db..f265c6f 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,7 @@ "dev": "webpack --config webpack.dev.js --progress --colors --watch", "heroku-prebuild": "bin/heroku", "build": "webpack --config webpack.prod.js --progress --colors --bail", - "start": "sequelize db:migrate && node app.js", - "doctoc": "doctoc --title='# Table of Contents' README.md" + "start": "sequelize db:migrate && node app.js" }, "dependencies": { "@passport-next/passport-openid": "^1.0.0", @@ -172,7 +171,6 @@ "babel-runtime": "^6.26.0", "copy-webpack-plugin": "^4.5.2", "css-loader": "^1.0.0", - "doctoc": "^1.4.0", "ejs-loader": "^0.3.1", "eslint": "^5.9.0", "eslint-config-standard": "^12.0.0", From 74fdd26ea057dab77e761614497d69d2c90a66d7 Mon Sep 17 00:00:00 2001 From: Claudius Date: Sun, 31 Mar 2019 18:57:26 +0200 Subject: [PATCH 16/36] integrating information from the old wiki Signed-off-by: Claudius --- CHANGELOG.md | 7 ++ docs/dev/getting-started.md | 49 ++++++++++- docs/slide-options.md | 159 ++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 docs/slide-options.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..97224d1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# CHANGELOG + +Please refer to the release notes published under +[`public/docs/release-notes.md`](public/docs/release-notes.md). + +These are also available on each CodiMD instance under +https://[domain-name]/release-notes diff --git a/docs/dev/getting-started.md b/docs/dev/getting-started.md index 6eb1b13..79a97d4 100644 --- a/docs/dev/getting-started.md +++ b/docs/dev/getting-started.md @@ -1,13 +1,54 @@ # Developer Notes +## Preparing for running the code + +**Notice:** *If you're into [docker](../setup/docker.md) or [heroku](../setup/heroku.md), +there's extra documents for those!* + +1. Clone the repository (Cloning is the preferred way, but you can also download + and unzip a release) +2. Enter the directory and run `bin/setup`, which will install npm dependencies + and create configs. The setup script is written in Bash, you would need bash + as a prerequisite. +3. Setup the [config file](../configuration-config-file.md) or set up + [environment variables](../configuration-env-vars.md). + + +## Running the Code + +Now that everything is in place, we can start CodiMD: + +4. `npm run build` will build the frontend bundle. It uses webpack to do that. +5. Run the server with `node app.js` + + +## Running the Code with Auto-Reload + +The commands above are fine for production, but you're a developer and surely +you want to change things. You would need to restart both commands whenever you +change something. Luckily, you can run these commands that will automatically +rebuild the frontend or restart the server if necessary. + +The commands will stay active in your terminal, so you will need multiple tabs +to run both at the same time. + +4. Use `npm run dev` if you want webpack to continuously rebuild the frontend + code. +5. To auto-reload the server, the easiest method is to install [nodemon](https://www.npmjs.com/package/nodemon) + and run `nodemon --watch app.js --watch lib --watch locales app.js`. + + ## Structure +The repository contains two parts: a server (backend) and a client (frontend). +most of the server code is in `/lib` and most of the client code is in `public`. + ```text -codimd/ -├── test/ --- test suite +codimd-server/ ├── docs/ --- documentation -├── lib/ --- server libraries -└── public/ --- client files +├── lib/ --- server code +├── test/ --- test suite +└── public/ --- client code ├── css/ --- css styles ├── docs/ --- default documents ├── js/ --- js scripts diff --git a/docs/slide-options.md b/docs/slide-options.md new file mode 100644 index 0000000..8da5dc1 --- /dev/null +++ b/docs/slide-options.md @@ -0,0 +1,159 @@ +## Slide Separators +If you're getting started with reveal.js slides, there are a few things you need to know. + +There are two types of slides, those that transition horizontally and those that transition vertically (subslides). + +The following separators are used for each in the hackmd syntax: +``` +# First Slide + +--- + +# Next slide + +---- + +## Subslide +``` +as you can see, horizontal transitions are separated by `---` and vertical transitions by `----` + +## Basic YAML header +It's possible to customise the slide options using the YAML header in the slide markdown. + +eg: +``` +--- +title: Example Slide +tags: presentation +slideOptions: + theme: solarized + transition: 'fade' + # parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg' +--- +``` +make sure to have two spaces only at the start of the listed slide options. + +you can comment out options with a `#` + +### Some other options +``` +# Display controls in the bottom right corner +controls: true + +# Display a presentation progress bar +progress: true + +# Set default timing of 2 minutes per slide +defaultTiming: 120 + +# Display the page number of the current slide +slideNumber: false + +# Push each slide change to the browser history +history: false + +# Enable keyboard shortcuts for navigation +keyboard: true + +# Enable the slide overview mode +overview: true + +# Vertical centering of slides +center: true + +# Enables touch navigation on devices with touch input +touch: true + +# Loop the presentation +loop: false + +# Change the presentation direction to be RTL +rtl: false + +# Randomizes the order of slides each time the presentation loads +shuffle: false + +# Turns fragments on and off globally +fragments: true + +# Flags if the presentation is running in an embedded mode, +# i.e. contained within a limited portion of the screen +embedded: false + +# Flags if we should show a help overlay when the questionmark +# key is pressed +help: true + +# Flags if speaker notes should be visible to all viewers +showNotes: false + +# Global override for autolaying embedded media (video/audio/iframe) +# - null: Media will only autoplay if data-autoplay is present +# - true: All media will autoplay, regardless of individual setting +# - false: No media will autoplay, regardless of individual setting +autoPlayMedia: null + +# Number of milliseconds between automatically proceeding to the +# next slide, disabled when set to 0, this value can be overwritten +# by using a data-autoslide attribute on your slides +autoSlide: 0 + +# Stop auto-sliding after user input +autoSlideStoppable: true + +# Use this method for navigation when auto-sliding +autoSlideMethod: Reveal.navigateNext + +# Enable slide navigation via mouse wheel +mouseWheel: false + +# Hides the address bar on mobile devices +hideAddressBar: true + +# Opens links in an iframe preview overlay +previewLinks: false + +# Transition style +transition: 'slide' +# none/fade/slide/convex/concave/zoom + +# Transition speed +transitionSpeed: 'default' +# default/fast/slow + +# Transition style for full page slide backgrounds +backgroundTransition: 'fade' +# none/fade/slide/convex/concave/zoom + +# Number of slides away from the current that are visible +viewDistance: 3 + +# Parallax background image +parallaxBackgroundImage: '' +# e.g. "'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg'" + +# Parallax background size +parallaxBackgroundSize: '' +# CSS syntax, e.g. "2100px 900px" + +# Number of pixels to move the parallax background per slide +# - Calculated automatically unless specified +# - Set to 0 to disable movement along an axis +parallaxBackgroundHorizontal: null +parallaxBackgroundVertical: null + +# The display mode that will be used to show slides +display: 'block' +``` + +## Customising individual slides + +custom background image: +``` +--- + + +#### testslide + +--- +``` From 54edec89000aec94cae325be3d5c85442ac21adf Mon Sep 17 00:00:00 2001 From: Claudius Date: Sun, 31 Mar 2019 20:10:32 +0200 Subject: [PATCH 17/36] striving for consistency across various docs Signed-off-by: Claudius --- .gitignore | 1 - README.md | 8 ++- docs/configuration-config-file.md | 3 +- docs/configuration-env-vars.md | 3 +- docs/dev/getting-started.md | 10 +-- docs/dev/ot.md | 12 +++- docs/dev/webpack.md | 9 ++- docs/guides/auth/github.md | 31 +++++----- docs/guides/auth/gitlab-self-hosted.md | 8 +-- docs/guides/auth/ldap-AD.md | 1 - docs/guides/auth/mattermost-self-hosted.md | 32 +++++----- docs/guides/auth/nextcloud.md | 4 +- docs/guides/auth/saml-onelogin.md | 52 +++++++--------- docs/guides/auth/saml.md | 62 +++++++++---------- docs/guides/auth/twitter.md | 42 ++++++------- .../guides/migrations-and-breaking-changes.md | 14 ++--- docs/guides/minio-image-upload.md | 14 ++--- docs/guides/s3-image-upload.md | 7 ++- docs/history.md | 3 +- docs/setup/cloudron.md | 3 +- docs/setup/docker.md | 7 ++- docs/setup/heroku.md | 3 +- docs/setup/kubernetes.md | 3 +- docs/setup/manual-setup.md | 4 +- docs/slide-options.md | 6 +- public/docs/features.md | 2 +- public/docs/release-notes.md | 2 +- 27 files changed, 175 insertions(+), 171 deletions(-) diff --git a/.gitignore b/.gitignore index 755e3f9..a31c314 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ composer.lock .idea/ Thumbs.db npm-debug.log -hackmd_io newrelic_agent.log logs/ tmp/ diff --git a/README.md b/README.md index 6e4d349..2cc9f46 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ CodiMD [![version][github-version-badge]][github-release-page] [![POEditor][poeditor-image]][poeditor-url] -CodiMD lets you create real-time collaborative markdown notes on all platforms. +CodiMD lets you create real-time collaborative markdown notes. + It is inspired by Hackpad, Etherpad and similar collaborative editors. This project originated with the team at [HackMD](https://hackmd.io) and now forked into its own organisation. [A longer writeup can be read in the history doc](docs/history.md). @@ -16,13 +17,14 @@ into its own organisation. [A longer writeup can be read in the history doc](doc ## Community and Contributions -We welcome contributions! There's a lot to do. If you would like to report bugs +We welcome contributions! There's a lot to do. If you would like to report bugs, the [issue tracker](github-issue-tracker) is the right place. If you want to help translating, find us on [POEditor](poeditor-url). To get started developing, take a look at the [docs/dev](docs/dev) directory. In any case: come talk to us, we'll be delighted to help you with the first steps. -To stay up to date with your installation it's recommended to join our [Matrix channel][matrix.org-url] or subscribe to the [release feed][github-release-feed]. +To stay up to date with our work it's recommended to join our +[Matrix channel][matrix.org-url] or subscribe to the [release feed][github-release-feed]. ## Installation / Upgrading diff --git a/docs/configuration-config-file.md b/docs/configuration-config-file.md index 4edbbf0..bc9c19e 100644 --- a/docs/configuration-config-file.md +++ b/docs/configuration-config-file.md @@ -1,4 +1,5 @@ -# Configuration Using Config file +Configuration Using Config file +=== You can choose to configure CodiMD with either a config file or with [environment variables](configuration-env-vars.md). The config file is processed diff --git a/docs/configuration-env-vars.md b/docs/configuration-env-vars.md index 2823a40..aa70b95 100644 --- a/docs/configuration-env-vars.md +++ b/docs/configuration-env-vars.md @@ -1,4 +1,5 @@ -# Configuration Using Environment variables +Configuration Using Environment variables +=== You can choose to configure CodiMD with either a [config file](configuration-config-file.md) or with environment variables. diff --git a/docs/dev/getting-started.md b/docs/dev/getting-started.md index 79a97d4..0752497 100644 --- a/docs/dev/getting-started.md +++ b/docs/dev/getting-started.md @@ -1,12 +1,12 @@ -# Developer Notes +Developer Notes +=== ## Preparing for running the code -**Notice:** *If you're into [docker](../setup/docker.md) or [heroku](../setup/heroku.md), -there's extra documents for those!* +**Notice:** *There's [specialised instructions for docker](../setup/docker.md) or [heroku](../setup/heroku.md), if you prefer running code this way!* -1. Clone the repository (Cloning is the preferred way, but you can also download - and unzip a release) +1. Clone the repository with `git clone https://github.com/codimd/server.git codimd-server` + (cloning is the preferred way, but you can also download and unzip a release) 2. Enter the directory and run `bin/setup`, which will install npm dependencies and create configs. The setup script is written in Bash, you would need bash as a prerequisite. diff --git a/docs/dev/ot.md b/docs/dev/ot.md index 71f7f78..924be69 100644 --- a/docs/dev/ot.md +++ b/docs/dev/ot.md @@ -1,6 +1,14 @@ -## Operational Transformation +Operational Transformation +=== From 0.3.2, we started supporting operational transformation. It makes concurrent editing safe and will not break up other users' operations. Additionally, now can show other clients' selections. -See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/) + +See more at [https://operational-transformation.github.io/](https://operational-transformation.github.io/) + +And even more in this 2010 article series: + +* https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs_21.html +* https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs_22.html +* https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs.html diff --git a/docs/dev/webpack.md b/docs/dev/webpack.md index 7b391f3..b05fd1d 100644 --- a/docs/dev/webpack.md +++ b/docs/dev/webpack.md @@ -1,4 +1,11 @@ -# Webpack Docs +Webpack +=== + +Webpack is a JavaScript build system for frontend code. You can find out all +about it on [the webpack website](https://webpack.js.org/). + +Here's how we're using it: + ## `webpack.common.js` This file contains all common definition for chunks and plugins, that are needed by the whole app. diff --git a/docs/guides/auth/github.md b/docs/guides/auth/github.md index 482bca3..d16b486 100644 --- a/docs/guides/auth/github.md +++ b/docs/guides/auth/github.md @@ -1,27 +1,24 @@ Authentication guide - GitHub === -***Note:** This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* +**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* 1. Sign-in or sign-up for a GitHub account 2. Navigate to developer settings in your GitHub account [here](https://github.com/settings/developers) and select the "OAuth Apps" tab -3. Click on the **New OAuth App** button, to create a new OAuth App: +3. Click on the **New OAuth App** button, to create a new OAuth App: + ![create-oauth-app](../../images/auth/create-oauth-app.png) -![create-oauth-app](../../images/auth/create-oauth-app.png) +4. Fill out the new OAuth application registration form, and click **Register Application** + ![register-oauth-application-form](../../images/auth/register-oauth-application-form.png) -4. Fill out the new OAuth application registration form, and click **Register Application** + **Note:** *The callback URL is /auth/github/callback* -![register-oauth-application-form](../../images/auth/register-oauth-application-form.png) - -*Note: The callback URL is /auth/github/callback* - -5. After successfully registering the application, you'll receive the Client ID and Client Secret for the application - -![application-page](../../images/auth/application-page.png) +5. After successfully registering the application, you'll receive the Client ID and Client Secret for the application + ![application-page](../../images/auth/application-page.png) 6. Add the Client ID and Client Secret to your config.json file or pass them as environment variables - * config.json: - ````javascript + * `config.json`: + ```js { "production": { "github": { @@ -30,9 +27,9 @@ Authentication guide - GitHub } } } - ```` + ``` * environment variables: - ```` - HMD_GITHUB_CLIENTID=3747d30eaccXXXXXXXXX - HMD_GITHUB_CLIENTSECRET=2a8e682948eee0c580XXXXXXXXXXXXXXXXXXXXXX + ```sh + CMD_GITHUB_CLIENTID=3747d30eaccXXXXXXXXX + CMD_GITHUB_CLIENTSECRET=2a8e682948eee0c580XXXXXXXXXXXXXXXXXXXXXX ```` diff --git a/docs/guides/auth/gitlab-self-hosted.md b/docs/guides/auth/gitlab-self-hosted.md index d19caf7..ea1ad6b 100644 --- a/docs/guides/auth/gitlab-self-hosted.md +++ b/docs/guides/auth/gitlab-self-hosted.md @@ -1,7 +1,7 @@ -# GitLab (self-hosted) +GitLab (self-hosted) === -***Note:** This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* +**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* 1. Sign in to your GitLab 2. Navigate to the application management page at `https://your.gitlab.domain/admin/applications` (admin permissions required) @@ -18,7 +18,7 @@ 6. In the `docker-compose.yml` add the following environment variables to `app:` `environment:` ``` -- HMD_DOMAIN=your.hackmd.domain +- HMD_DOMAIN=your.codimd.domain - HMD_URL_ADDPORT=443 - HMD_PROTOCOL_USESSL=true - HMD_GITLAB_BASEURL=https://your.gitlab.domain @@ -27,6 +27,6 @@ ``` 7. Run `docker-compose up -d` to apply your settings. -8. Sign in to your HackMD using your GitLab ID: +8. Sign in to your CodiMD using your GitLab ID: ![Sign in via GitLab](../../images/auth/gitlab-sign-in.png) diff --git a/docs/guides/auth/ldap-AD.md b/docs/guides/auth/ldap-AD.md index 77521db..e74121f 100644 --- a/docs/guides/auth/ldap-AD.md +++ b/docs/guides/auth/ldap-AD.md @@ -1,7 +1,6 @@ AD LDAP auth === - To setup your CodiMD instance with Active Directory you need the following configs: ``` diff --git a/docs/guides/auth/mattermost-self-hosted.md b/docs/guides/auth/mattermost-self-hosted.md index e305059..d828039 100644 --- a/docs/guides/auth/mattermost-self-hosted.md +++ b/docs/guides/auth/mattermost-self-hosted.md @@ -1,33 +1,29 @@ Authentication guide - Mattermost (self-hosted) === -*Note: The Mattermost setup portion of this document is just a quick guide. See the [official documentation](https://docs.mattermost.com/developer/oauth-2-0-applications.html) for more details.* +**Note:** *The Mattermost setup portion of this document is just a quick guide. See the [official documentation](https://docs.mattermost.com/developer/oauth-2-0-applications.html) for more details.* This guide uses the generic OAuth2 module for compatibility with Mattermost version 5.0 and above. 1. Sign-in with an administrator account to your Mattermost instance -2. Make sure **OAuth 2.0 Service Provider** is enabled in the Main Menu (menu button next to your username in the top left corner) --> System Console --> Custom Integrations menu, which you can find at `https://your.mattermost.domain/admin_console/integrations/custom` - -![mattermost-enable-oauth2](../../images/auth/mattermost-enable-oauth2.png) +2. Make sure **OAuth 2.0 Service Provider** is enabled in the Main Menu (menu button next to your username in the top left corner) --> System Console --> Custom Integrations menu, which you can find at `https://your.mattermost.domain/admin_console/integrations/custom` + ![mattermost-enable-oauth2](../../images/auth/mattermost-enable-oauth2.png) 3. Navigate to the OAuth integration settings through Main Menu --> Integrations --> OAuth 2.0 Applications, at `https://your.mattermost.domain/yourteam/integrations/oauth2-apps` -4. Click on the **Add OAuth 2.0 Application** button to add a new OAuth application +4. Click on the **Add OAuth 2.0 Application** button to add a new OAuth application + ![mattermost-oauth-app-add](../../images/auth/mattermost-oauth-app-add.png) -![mattermost-oauth-app-add](../../images/auth/mattermost-oauth-app-add.png) - -5. Fill out the form and click **Save** - -![mattermost-oauth-app-form](../../images/auth/mattermost-oauth-app-form.png) +5. Fill out the form and click **Save** + ![mattermost-oauth-app-form](../../images/auth/mattermost-oauth-app-form.png) *Note: The callback URL is \/auth/oauth2/callback* -6. After saving the application, you'll receive the Client ID and Client Secret - -![mattermost-oauth-app-done](../../images/auth/mattermost-oauth-app-done.png) +6. After saving the application, you'll receive the Client ID and Client Secret + ![mattermost-oauth-app-done](../../images/auth/mattermost-oauth-app-done.png) 7. Add the Client ID and Client Secret to your config.json file or pass them as environment variables - * config.json: - ````javascript + * `config.json`: + ```javascript { "production": { "oauth2": { @@ -43,9 +39,9 @@ This guide uses the generic OAuth2 module for compatibility with Mattermost vers } } } - ```` + ``` * environment variables: - ```` + ```sh CMD_OAUTH2_BASEURL=https://your.mattermost.domain CMD_OAUTH2_USER_PROFILE_URL=https://your.mattermost.domain/api/v4/users/me CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR=id @@ -55,4 +51,4 @@ This guide uses the generic OAuth2 module for compatibility with Mattermost vers CMD_OAUTH2_AUTHORIZATION_URL=https://your.mattermost.domain/oauth/authorize CMD_OAUTH2_CLIENT_ID=ii4p1u3jz7dXXXXXXXXXXXXXXX CMD_OAUTH2_CLIENT_SECRET=mqzzx6fydbXXXXXXXXXXXXXXXX - ```` + ``` diff --git a/docs/guides/auth/nextcloud.md b/docs/guides/auth/nextcloud.md index 42db8b4..3bf86d3 100644 --- a/docs/guides/auth/nextcloud.md +++ b/docs/guides/auth/nextcloud.md @@ -9,10 +9,10 @@ This guide uses the generic OAuth2 module for compatibility with Nextcloud 13 an 2. Navigate to the OAuth integration settings: Profile Icon (top right) --> Settings Then choose Security Settings from the *Administration* part of the list - Don't confuse this with Personal Security Settings, where you would change your personal password! - At the top there's OAuth 2.0-Clients. + At the top there's OAuth 2.0-Clients. ![Where to find OAuth2 in Nextcloud](../../images/auth/nextcloud-oauth2-1-settings.png) -3. Add your CodiMD instance by giving it a *name* (perhaps CodiMD, but could be anything) and a *Redirection-URI*. The Redirection-URI will be `\/auth/oauth2/callback`. Click Add. +3. Add your CodiMD instance by giving it a *name* (perhaps CodiMD, but could be anything) and a *Redirection-URI*. The Redirection-URI will be `\/auth/oauth2/callback`. Click Add. ![Adding a client to Nextcloud](../../images/auth/nextcloud-oauth2-2-client-add.png) diff --git a/docs/guides/auth/saml-onelogin.md b/docs/guides/auth/saml-onelogin.md index 14ce61a..785e36b 100644 --- a/docs/guides/auth/saml-onelogin.md +++ b/docs/guides/auth/saml-onelogin.md @@ -1,40 +1,35 @@ Authentication guide - SAML (OneLogin) === -***Note:** This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* +**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* 1. Sign-in or sign-up for an OneLogin account. (available free trial for 2 weeks) 2. Go to the administration page. -3. Select the **APPS** menu and click on the **Add Apps**. +3. Select the **APPS** menu and click on the **Add Apps**. + ![onelogin-add-app](../../images/auth/onelogin-add-app.png) -![onelogin-add-app](../../images/auth/onelogin-add-app.png) +4. Find "SAML Test Connector (SP)" for template of settings and select it. + ![onelogin-select-template](../../images/auth/onelogin-select-template.png) -4. Find "SAML Test Connector (SP)" for template of settings and select it. - -![onelogin-select-template](../../images/auth/onelogin-select-template.png) - -5. Edit display name and icons for OneLogin dashboard as you want, and click **SAVE**. - -![onelogin-edit-app-name](../../images/auth/onelogin-edit-app-name.png) +5. Edit display name and icons for OneLogin dashboard as you want, and click **SAVE**. + ![onelogin-edit-app-name](../../images/auth/onelogin-edit-app-name.png) 6. After that other tabs will appear, click the **Configuration**, and fill out the below items, and click **SAVE**. - * RelayState: The base URL of your hackmd, which is issuer. (last slash is not needed) - * ACS (Consumer) URL Validator: The callback URL of your hackmd. (serverurl + /auth/saml/callback) + * RelayState: The base URL of your CodiMD, which is issuer. (last slash is not needed) + * ACS (Consumer) URL Validator: The callback URL of your CodiMD. (serverurl + /auth/saml/callback) * ACS (Consumer) URL: same as above. - * Login URL: login URL(SAML requester) of your hackmd. (serverurl + /auth/saml) - -![onelogin-edit-sp-metadata](../../images/auth/onelogin-edit-sp-metadata.png) + * Login URL: login URL(SAML requester) of your CopiMD. (serverurl + /auth/saml) + ![onelogin-edit-sp-metadata](../../images/auth/onelogin-edit-sp-metadata.png) 7. The registration is completed. Next, click **SSO** and copy or download the items below. * X.509 Certificate: Click **View Details** and **DOWNLOAD** or copy the content of certificate ....(A) - * SAML 2.0 Endpoint (HTTP): Copy the URL ....(B) + * SAML 2.0 Endpoint (HTTP): Copy the URL ....(B) + ![onelogin-copy-idp-metadata](../../images/auth/onelogin-copy-idp-metadata.png) -![onelogin-copy-idp-metadata](../../images/auth/onelogin-copy-idp-metadata.png) - -8. In your hackmd server, create IdP certificate file from (A) +8. In your CodiMD server, create IdP certificate file from (A) 9. Add the IdP URL (B) and the Idp certificate file path to your config.json file or pass them as environment variables. - * config.json: - ````javascript + * `config.json`: + ```javascript { "production": { "saml": { @@ -43,12 +38,11 @@ Authentication guide - SAML (OneLogin) } } } - ```` + ``` * environment variables - ```` - HMD_SAML_IDPSSOURL=https://*******.onelogin.com/trust/saml2/http-post/sso/****** - HMD_SAML_IDPCERT=/path/to/idp_cert.pem - ```` -10. Try sign-in with SAML from your hackmd sign-in button or OneLogin dashboard (like the screenshot below). - -![onelogin-use-dashboard](../../images/auth/onelogin-use-dashboard.png) + ```sh + CMD_SAML_IDPSSOURL=https://*******.onelogin.com/trust/saml2/http-post/sso/****** + CMD_SAML_IDPCERT=/path/to/idp_cert.pem + ``` +10. Try sign-in with SAML from your CodiMD sign-in button or OneLogin dashboard (like the screenshot below). + ![onelogin-use-dashboard](../../images/auth/onelogin-use-dashboard.png) diff --git a/docs/guides/auth/saml.md b/docs/guides/auth/saml.md index 7f63b74..3a64f5f 100644 --- a/docs/guides/auth/saml.md +++ b/docs/guides/auth/saml.md @@ -1,7 +1,7 @@ Authentication guide - SAML === -***Note:** This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* +**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* The basic procedure is the same as the case of OneLogin which is mentioned in [OneLogin-Guide](./saml-onelogin.md). If you want to match your IdP, you can use more configurations as below. @@ -9,36 +9,36 @@ The basic procedure is the same as the case of OneLogin which is mentioned in [O * {{your-serverurl}}/auth/saml/metadata * _Note: If not accessible from IdP, download to local once and upload to IdP._ * Change the value of `issuer`, `identifierFormat` to match your IdP. - * `issuer`: A unique id to identify the application to the IdP, which is the base URL of your HackMD as default + * `issuer`: A unique id to identify the application to the IdP, which is the base URL of your CodiMD as default * `identifierFormat`: A format of unique id to identify the user of IdP, which is the format based on email address as default. It is recommend that you use as below. * urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress (default) * urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified - * config.json: - ````javascript + * `config.json`: + ```javascript { "production": { "saml": { /* omitted */ - "issuer": "myhackmd" + "issuer": "mycodimd" "identifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" } } } - ```` + ``` * environment variables - ```` - HMD_SAML_ISSUER=myhackmd - HMD_SAML_IDENTIFIERFORMAT=urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified - ```` + ``` + CMD_SAML_ISSUER=mycodimd + CMD_SAML_IDENTIFIERFORMAT=urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + ``` * Change mapping of attribute names to customize the displaying user name and email address to match your IdP. * `attribute`: A dictionary to map attribute names - * `attribute.id`: A primary key of user table for your HackMD - * `attribute.username`: Attribute name of displaying user name on HackMD + * `attribute.id`: A primary key of user table for your CodiMD + * `attribute.username`: Attribute name of displaying user name on CodiMD * `attribute.email`: Attribute name of email address, which will be also used for Gravatar * _Note: Default value of all attributes is NameID of SAML response, which is email address if `identifierFormat` is default._ - * config.json: - ````javascript + * `config.json`: + ```javascript { "production": { "saml": { @@ -51,35 +51,35 @@ The basic procedure is the same as the case of OneLogin which is mentioned in [O } } } - ```` + ``` * environment variables - ```` - HMD_SAML_ATTRIBUTE_ID=sAMAccountName - HMD_SAML_ATTRIBUTE_USERNAME=nickName - HMD_SAML_ATTRIBUTE_EMAIL=mail - ```` + ```sh + CMD_SAML_ATTRIBUTE_ID=sAMAccountName + CMD_SAML_ATTRIBUTE_USERNAME=nickName + CMD_SAML_ATTRIBUTE_EMAIL=mail + ``` * If you want to control permission by group membership, add group attribute name and required group (allowed) or external group (not allowed). * `groupAttribute`: An attribute name of group membership - * `requiredGroups`: Group names array for allowed access to HackMD. Use vertical bar to separate for environment variables. - * `externalGroups`: Group names array for not allowed access to HackMD. Use vertical bar to separate for environment variables. + * `requiredGroups`: Group names array for allowed access to CodiMD. Use vertical bar to separate for environment variables. + * `externalGroups`: Group names array for not allowed access to CodiMD. Use vertical bar to separate for environment variables. * _Note: Evaluates `externalGroups` first_ - * config.json: - ````javascript + * `config.json`: + ```javascript { "production": { "saml": { /* omitted */ "groupAttribute": "memberOf", - "requiredGroups": [ "hackmd-users", "board-members" ], + "requiredGroups": [ "codimd-users", "board-members" ], "externalGroups": [ "temporary-staff" ] } } } - ```` + ``` * environment variables - ```` - HMD_SAML_GROUPATTRIBUTE=memberOf - HMD_SAML_REQUIREDGROUPS=hackmd-users|board-members - HMD_SAML_EXTERNALGROUPS=temporary-staff - ```` + ```sh + CMD_SAML_GROUPATTRIBUTE=memberOf + CMD_SAML_REQUIREDGROUPS=codimd-users|board-members + CMD_SAML_EXTERNALGROUPS=temporary-staff + ``` diff --git a/docs/guides/auth/twitter.md b/docs/guides/auth/twitter.md index da35a4e..1973515 100644 --- a/docs/guides/auth/twitter.md +++ b/docs/guides/auth/twitter.md @@ -1,33 +1,29 @@ Authentication guide - Twitter === -***Note:** This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* +**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* 1. Sign-in or sign-up for a Twitter account 2. Go to the Twitter Application management page [here](https://apps.twitter.com/) -3. Click on the **Create New App** button to create a new Twitter app: +3. Click on the **Create New App** button to create a new Twitter app: + ![create-twitter-app](../../images/auth/create-twitter-app.png) -![create-twitter-app](../../images/auth/create-twitter-app.png) +4. Fill out the create application form, check the developer agreement box, and click **Create Your Twitter Application** + ![register-twitter-application](../../images/auth/register-twitter-application.png) -4. Fill out the create application form, check the developer agreement box, and click **Create Your Twitter Application** + *Note: you may have to register your phone number with Twitter to create a Twitter application* -![register-twitter-application](../../images/auth/register-twitter-application.png) + To do this Click your profile icon --> Settings and privacy --> Mobile --> Select Country/region --> Enter phone number --> Click Continue -*Note: you may have to register your phone number with Twitter to create a Twitter application* +5. After you receive confirmation that the Twitter application was created, click **Keys and Access Tokens** + ![twitter-app-confirmation](../../images/auth/twitter-app-confirmation.png) -To do this Click your profile icon --> Settings and privacy --> Mobile --> Select Country/region --> Enter phone number --> Click Continue +6. Obtain your Twitter Consumer Key and Consumer Secret + ![twitter-app-keys](../../images/auth/twitter-app-keys.png) -5. After you receive confirmation that the Twitter application was created, click **Keys and Access Tokens** - -![twitter-app-confirmation](../../images/auth/twitter-app-confirmation.png) - -6. Obtain your Twitter Consumer Key and Consumer Secret - -![twitter-app-keys](../../images/auth/twitter-app-keys.png) - -7. Add your Consumer Key and Consumer Secret to your config.json file or pass them as environment variables: - * config.json: - ````javascript +7. Add your Consumer Key and Consumer Secret to your `config.json` file or pass them as environment variables: + * `config.json`: + ```javascript { "production": { "twitter": { @@ -36,9 +32,9 @@ To do this Click your profile icon --> Settings and privacy --> Mobile --> Sele } } } - ```` + ``` * environment variables: - ```` - HMD_TWITTER_CONSUMERKEY=esTCJFXXXXXXXXXXXXXXXXXXX - HMD_TWITTER_CONSUMERSECRET=zpCs4tU86pRVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - ```` + ```sh + CMD_TWITTER_CONSUMERKEY=esTCJFXXXXXXXXXXXXXXXXXXX + CMD_TWITTER_CONSUMERSECRET=zpCs4tU86pRVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + ``` diff --git a/docs/guides/migrations-and-breaking-changes.md b/docs/guides/migrations-and-breaking-changes.md index f5a416f..776055b 100644 --- a/docs/guides/migrations-and-breaking-changes.md +++ b/docs/guides/migrations-and-breaking-changes.md @@ -1,4 +1,5 @@ -# Migrations and Notable Changes +Migrations and Notable Changes +=== ## Migrating to 1.3.2 @@ -37,22 +38,15 @@ We deprecated the older lower case config style and moved on to camel case style ## Migrating to 0.5.0 -[**migration-to-0.5.0**](https://github.com/hackmdio/migration-to-0.5.0) +[migration-to-0.5.0 migration tool](https://github.com/hackmdio/migration-to-0.5.0) We don't use LZString to compress socket.io data and DB data after version 0.5.0. Please run the migration tool if you're upgrading from the old version. ## Migrating to 0.4.0 -[**migration-to-0.4.0**](https://github.com/hackmdio/migration-to-0.4.0) +[migration-to-0.4.0 migration tool](https://github.com/hackmdio/migration-to-0.4.0) We've dropped MongoDB after version 0.4.0. So here is the migration tool for you to transfer the old DB data to the new DB. This tool is also used for official service. - -## Operational Transformation in 0.3.2 - -From 0.3.2, we started supporting operational transformation. -It makes concurrent editing safe and will not break up other users' operations. -Additionally, now can show other clients' selections. -See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/) diff --git a/docs/guides/minio-image-upload.md b/docs/guides/minio-image-upload.md index 1544e24..258b0d9 100644 --- a/docs/guides/minio-image-upload.md +++ b/docs/guides/minio-image-upload.md @@ -1,7 +1,7 @@ Minio Guide for CodiMD === -***Note:** This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* +**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* 1. First of all you need to setup Minio itself. @@ -9,7 +9,7 @@ Minio Guide for CodiMD production setup. For checking it out and development purposes a non-persistent setup is enough: - ```console + ```sh docker run --name test-minio --rm -d -p 9000:9000 minio/minio server /data ``` @@ -18,7 +18,7 @@ Minio Guide for CodiMD 2. Next step is to get the credentials form the container: - ``` + ```sh docker logs test-minio ``` @@ -28,7 +28,7 @@ Minio Guide for CodiMD ![minio default view](../images/minio-image-upload/default-view.png) -4. Create a bucket for HackMD +4. Create a bucket for CodiMD ![minio create bucket](../images/minio-image-upload/create-bucket.png) @@ -40,7 +40,7 @@ Minio Guide for CodiMD ![minio policy adding](../images/minio-image-upload/create-policy.png) *Add policy for uploads* -6. Set credentials and configs for Minio in HackMD's `config.json` +6. Set credentials and configs for Minio in CodiMD's `config.json` ```JSON "minio": { @@ -58,7 +58,7 @@ Minio Guide for CodiMD 7. Set bucket name ```JSON - "s3bucket": "hackmd" + "s3bucket": "codimd" ``` 8. Set upload type. @@ -79,7 +79,7 @@ Minio Guide for CodiMD "port": 9000, "secure": false }, - "s3bucket": "hackmd", + "s3bucket": "codimd", "imageuploadtype": "minio" } ``` diff --git a/docs/guides/s3-image-upload.md b/docs/guides/s3-image-upload.md index 2943701..7ca8dd1 100644 --- a/docs/guides/s3-image-upload.md +++ b/docs/guides/s3-image-upload.md @@ -1,6 +1,7 @@ -# Guide - Setup CodiMD S3 image upload +Guide - Setup CodiMD S3 image upload +=== -***Note:** This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* +**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!* 1. Go to [AWS S3 console](https://console.aws.amazon.com/s3/home) and create a new bucket. @@ -76,7 +77,7 @@ } ``` -9. In additional to edit `config.json` directly, you could also try [environment variable](https://github.com/codimd/server#environment-variables-will-overwrite-other-server-configs). +9. In additional to edit `config.json` directly, you could also try [environment variables](../configuration-env-vars.md). ## Related Tools diff --git a/docs/history.md b/docs/history.md index b1dfde8..cd12a36 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,4 +1,5 @@ -# History of CodiMD +History of CodiMD +=== ## It started with HackMD diff --git a/docs/setup/cloudron.md b/docs/setup/cloudron.md index edab7d0..aa707dc 100644 --- a/docs/setup/cloudron.md +++ b/docs/setup/cloudron.md @@ -1,4 +1,5 @@ -## Cloudron +Cloudron +=== Install CodiMD on [Cloudron](https://cloudron.io): diff --git a/docs/setup/docker.md b/docs/setup/docker.md index defe467..1a9fd89 100644 --- a/docs/setup/docker.md +++ b/docs/setup/docker.md @@ -1,5 +1,6 @@ +CodiMD by docker container +=== -## CodiMD by docker container [![Try in PWD](https://cdn.rawgit.com/play-with-docker/stacks/cff22438/assets/images/button.png)](http://play-with-docker.com?stack=https://github.com/codimd/container/raw/master/docker-compose.yml&stack_name=codimd) @@ -14,9 +15,9 @@ The easiest way to setup CodiMD using docker are using the following three commands: -```console +```sh git clone https://github.com/codimd/container.git cd codimd-container docker-compose up ``` -Read more about it in the [container repository…](https://github.com/codimd/container) +Read more about it in the [container repository](https://github.com/codimd/container). diff --git a/docs/setup/heroku.md b/docs/setup/heroku.md index 86b4eca..9dc3bb3 100644 --- a/docs/setup/heroku.md +++ b/docs/setup/heroku.md @@ -1,4 +1,5 @@ -## Heroku Deployment +Heroku Deployment +=== You can quickly setup a sample Heroku CodiMD application by clicking the button below. diff --git a/docs/setup/kubernetes.md b/docs/setup/kubernetes.md index 400e2b4..db59685 100644 --- a/docs/setup/kubernetes.md +++ b/docs/setup/kubernetes.md @@ -1,4 +1,5 @@ -## Kubernetes +Kubernetes +=== To install use `helm install stable/hackmd`. diff --git a/docs/setup/manual-setup.md b/docs/setup/manual-setup.md index 25869ee..721d5bf 100644 --- a/docs/setup/manual-setup.md +++ b/docs/setup/manual-setup.md @@ -1,4 +1,5 @@ -# Manual Installation +Manual Installation +=== ## Requirements on your server @@ -8,6 +9,7 @@ - `libssl-dev` for building scrypt (see [here](https://github.com/ml1nk/node-scrypt/blob/master/README.md#installation-instructions) for further information) - For **building** CodiMD we recommend to use a machine with at least **2GB** RAM + ## Instructions 1. Download a release and unzip or clone into a directory diff --git a/docs/slide-options.md b/docs/slide-options.md index 8da5dc1..d127783 100644 --- a/docs/slide-options.md +++ b/docs/slide-options.md @@ -1,9 +1,11 @@ -## Slide Separators +Slide Separators +=== + If you're getting started with reveal.js slides, there are a few things you need to know. There are two types of slides, those that transition horizontally and those that transition vertically (subslides). -The following separators are used for each in the hackmd syntax: +The following separators are used for each in the CodiMD syntax: ``` # First Slide diff --git a/public/docs/features.md b/public/docs/features.md index e17619f..9f61992 100644 --- a/public/docs/features.md +++ b/public/docs/features.md @@ -67,7 +67,7 @@ There are four possible options: Notes can be embedded as follows: ```xml - + ``` ## [Slide Mode](./slide-example): diff --git a/public/docs/release-notes.md b/public/docs/release-notes.md index 6c30a0f..5eb2802 100644 --- a/public/docs/release-notes.md +++ b/public/docs/release-notes.md @@ -184,7 +184,7 @@ Release Notes --- ### Announcement -* HackMD CE is renamed to CodiMD to prevent confusion. [For details see here](https://github.com/codimd/server#hackmd-ce-became-codimd) +* HackMD CE is renamed to CodiMD to prevent confusion. [For details see here](https://github.com/codimd/server/tree/master/docs/history.md) ### Enhancements * Show full title by hovering over to table of contents entries From 33b22cf26fc25feef5c4fb8d76a04c880d935bfe Mon Sep 17 00:00:00 2001 From: Claudius Date: Mon, 1 Apr 2019 00:50:06 +0200 Subject: [PATCH 18/36] breaking up config docs into sections Signed-off-by: Claudius --- docs/configuration-config-file.md | 160 +++++++++++++++------ docs/configuration-env-vars.md | 226 ++++++++++++++++++++++++------ 2 files changed, 301 insertions(+), 85 deletions(-) diff --git a/docs/configuration-config-file.md b/docs/configuration-config-file.md index bc9c19e..41122aa 100644 --- a/docs/configuration-config-file.md +++ b/docs/configuration-config-file.md @@ -4,62 +4,144 @@ Configuration Using Config file You can choose to configure CodiMD with either a config file or with [environment variables](configuration-env-vars.md). The config file is processed in [`lib/config/index.js`](lib/config/index.js) - so this is the first -place to look if anything is missing not obvious from this document. +place to look if anything is missing not obvious from this document. The +default values are defined in [`lib/config/default.js`](lib/config/default.js), +in case you wonder if you even need to override it. Environment variables take precedence over configurations from the config files. To get started, it is a good idea to take the `config.json.example` and copy it to `config.json` before filling in your own details. -[//]: # (TODO split up into chunks) +## Node.JS | variables | example values | description | | --------- | ------ | ----------- | | `debug` | `true` or `false` | set debug mode, show more logs | -| `domain` | `localhost` | domain name | -| `urlPath` | `codimd` | sub URL path, like `www.example.com/` | -| `host` | `localhost` | host to listen on | -| `port` | `80` | web app port | -| `path` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `host` and `port` are ignored) | -| `loglevel` | `info` | Defines what kind of logs are provided to stdout. | -| `allowOrigin` | `['localhost']` | domain name whitelist | -| `useSSL` | `true` or `false` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) | -| `hsts` | `{"enable": true, "maxAgeSeconds": 31536000, "includeSubdomains": true, "preload": true}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) | -| `csp` | `{"enable": true, "directives": {"scriptSrc": "trustworthy-scripts.example.com"}, "upgradeInsecureRequests": "auto", "addDefaults": true}` | Configures [Content Security Policy](https://helmetjs.github.io/docs/csp/). Directives are passed to Helmet - see [their documentation](https://helmetjs.github.io/docs/csp/) for more information on the format. Some defaults are added to the configured values so that the application doesn't break. To disable this behaviour, set `addDefaults` to `false`. Further, if `usecdn` is on, some CDN locations are allowed too. By default (`auto`), insecure (HTTP) requests are upgraded to HTTPS via CSP if `useSSL` is on. To change this behaviour, set `upgradeInsecureRequests` to either `true` or `false`. | -| `protocolUseSSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) | -| `urlAddPort` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) | -| `useCDN` | `true` or `false` | set to use CDN resources or not (default is `true`) | -| `allowAnonymous` | `true` or `false` | set to allow anonymous usage (default is `true`) | -| `allowAnonymousEdits` | `true` or `false` | if `allowAnonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) | -| `allowFreeURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL | -| `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` | -| `defaultPermission` | `freely`, `editable`, `limited`, `locked`, `protected` or `private` | set notes default permission (only applied on signed users) | -| `dbURL` | `mysql://localhost:3306/database` | set the db URL; if set, then db config (below) won't be applied | + + +## CodiMD basics + +| variables | example values | description | +| --------- | ------ | ----------- | +| `allowPDFExport` | `true` | Whether or not PDF export is offered. | | `db` | `{ "dialect": "sqlite", "storage": "./db.codimd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) | -| `sslKeyPath` | `./cert/client.key` | SSL key path1 (only need when you set `useSSL`) | -| `sslCertPath` | `./cert/codimd_io.crt` | SSL cert path1 (only need when you set `useSSL`) | -| `sslCAPath` | `['./cert/COMODORSAAddTrustCA.crt']` | SSL ca chain1 (only need when you set `useSSL`) | -| `dhParamPath` | `./cert/dhparam.pem` | SSL dhparam path1 (only need when you set `useSSL`) | -| `tmpPath` | `./tmp/` | temp directory path1 | -| `defaultNotePath` | `./public/default.md` | default note file path1 | -| `docsPath` | `./public/docs` | docs directory path1 | -| `viewPath` | `./public/views` | template directory path1 | -| `uploadsPath` | `./public/uploads` | uploads directory1 - needs to be persistent when you use imageUploadType `filesystem` | -| `sessionName` | `connect.sid` | cookie session name | -| `sessionSecret` | `secret` | cookie session secret | -| `sessionLife` | `14 * 24 * 60 * 60 * 1000` | cookie session life | +| `dbURL` | `mysql://localhost:3306/database` | set the db URL; if set, then db config (below) won't be applied | +| `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` | +| `loglevel` | `info` | Defines what kind of logs are provided to stdout. | +| `imageUploadType` | `imgur`, `s3`, `minio`, `azure` or `filesystem`(default) | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md)| +| `sourceURL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | | `staticCacheTime` | `1 * 24 * 60 * 60 * 1000` | static file cache time | | `heartbeatInterval` | `5000` | socket.io heartbeat interval | | `heartbeatTimeout` | `10000` | socket.io heartbeat timeout | | `documentMaxLength` | `100000` | note max length | -| `email` | `true` or `false` | set to allow email signin | -| `oauth2` | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details!| -| `allowEmailRegister` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) | + + +## CodiMD paths stuff + +these are rarely used for various reasons. + +| variables | example values | description | +| --------- | ------ | ----------- | +| `defaultNotePath` | `./public/default.md` | default note file path1, empty notes will be created with this template. | +| `dhParamPath` | `./cert/dhparam.pem` | SSL dhparam path1 (only need when you set `useSSL`) | +| `sslCAPath` | `['./cert/COMODORSAAddTrustCA.crt']` | SSL ca chain1 (only need when you set `useSSL`) | +| `sslCertPath` | `./cert/codimd_io.crt` | SSL cert path1 (only need when you set `useSSL`) | +| `sslKeyPath` | `./cert/client.key` | SSL key path1 (only need when you set `useSSL`) | +| `tmpPath` | `./tmp/` | temp directory path1 | +| `docsPath` | `./public/docs` | docs directory path1 | +| `viewPath` | `./public/views` | template directory path1 | +| `uploadsPath` | `./public/uploads` | uploads directory1 - needs to be persistent when you use imageUploadType `filesystem` | + + +## CodiMD Location + +| variables | example values | description | +| --------- | ------ | ----------- | +| `domain` | `localhost` | domain name | +| `urlPath` | `codimd` | sub URL path, like `www.example.com/` | +| `host` | `localhost` | interface/ip to listen on | +| `port` | `80` | port to listen on | +| `path` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `host` and `port` are ignored) | +| `protocolUseSSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) | +| `useSSL` | `true` or `false` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) | +| `urlAddPort` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) | +| `allowOrigin` | `['localhost']` | domain name whitelist | + + +## CSP and HSTS + +| variables | example values | description | +| --------- | ------ | ----------- | +| `hsts` | `{"enable": true, "maxAgeSeconds": 31536000, "includeSubdomains": true, "preload": true}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) | +| `csp` | `{"enable": true, "directives": {"scriptSrc": "trustworthy-scripts.example.com"}, "upgradeInsecureRequests": "auto", "addDefaults": true}` | Configures [Content Security Policy](https://helmetjs.github.io/docs/csp/). Directives are passed to Helmet - see [their documentation](https://helmetjs.github.io/docs/csp/) for more information on the format. Some defaults are added to the configured values so that the application doesn't break. To disable this behaviour, set `addDefaults` to `false`. Further, if `usecdn` is on, some CDN locations are allowed too. By default (`auto`), insecure (HTTP) requests are upgraded to HTTPS via CSP if `useSSL` is on. To change this behaviour, set `upgradeInsecureRequests` to either `true` or `false`. | + +## Privacy and External Requests + +| variables | example values | description | +| --------- | ------ | ----------- | | `allowGravatar` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance | -| `imageUploadType` | `imgur`, `s3`, `minio`, `azure` or `filesystem`(default) | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md)| -| `minio` | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also checkout our [Minio Image Upload Guide](docs/guides/minio-image-upload.md) | +| `useCDN` | `true` or `false` | set to use CDN resources or not (default is `true`) | + +## Users and Privileges + +| variables | example values | description | +| --------- | ------ | ----------- | +| `allowAnonymous` | `true` or `false` | set to allow anonymous usage (default is `true`) | +| `allowAnonymousEdits` | `true` or `false` | if `allowAnonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) | +| `allowFreeURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL | +| `defaultPermission` | `freely`, `editable`, `limited`, `locked`, `protected` or `private` | set notes default permission (only applied on signed users) | +| `sessionName` | `connect.sid` | cookie session name | +| `sessionLife` | `14 * 24 * 60 * 60 * 1000` | cookie session life | +| `sessionSecret` | `secret` | cookie session secret | If none is set, one will randomly generated on each startup, meaning all your users will be logged out. | + + +## Login methods + +Most of these have never been documented for the config.json, feel free to expand these + +### Email (local account) + +| variables | example values | description | +| --------- | ------ | ----------- | +| `email` | `true` or `false` | set to allow email signin | +| `allowEmailRegister` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) | + +### Dropbox Login +### Facebook Login +### GitHub Login +### GitLab Login +### Google Login +### LDAP Login +### Mattermost Login +### OAuth2 Login + +| variables | example values | description | +| --------- | ------ | ----------- | +| `oauth2` | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details!| + +### SAML Login +### Twitter Login + + +## Upload Storage + +Most of these have never been documented for the config.json, feel free to expand these + + +### Amazon S3 + +| variables | example values | description | +| --------- | ------ | ----------- | | `s3` | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageuploadtype` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) | | `s3bucket` | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` or `minio` | -| `sourceURL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | + +### Azure Blob Storage +### imgur +### Minio + +| variables | example values | description | +| --------- | ------ | ----------- | +| `minio` | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also checkout our [Minio Image Upload Guide](docs/guides/minio-image-upload.md) | 1: relative paths are based on CodiMD's base directory diff --git a/docs/configuration-env-vars.md b/docs/configuration-env-vars.md index aa70b95..5510736 100644 --- a/docs/configuration-env-vars.md +++ b/docs/configuration-env-vars.md @@ -5,56 +5,141 @@ You can choose to configure CodiMD with either a [config file](configuration-config-file.md) or with environment variables. Environment variables are processed in [`lib/config/environment.js`](lib/config/environment.js) - so this is the first -place to look if anything is missing not obvious from this document. +place to look if anything is missing not obvious from this document. The +default values are defined in [`lib/config/default.js`](lib/config/default.js), +in case you wonder if you even need to override it. Environment variables take precedence over configurations from the config files. They generally start with `CMD_` for our own options, but we also list node-specific options you can configure this way. -[//]: # (TODO split up into chunks) +## Node.JS -| variable | example value | description | default | -| -------- | ------------- | ----------- | ------- | -| `NODE_ENV` | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) | +| variable | example value | description | +| -------- | ------------- | ----------- | +| `NODE_ENV` | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) | | `DEBUG` | `true` or `false` | set debug mode; show more logs | + + +## CodiMD basics + +defaultNotePath can't be set from env-vars + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_ALLOW_PDF_EXPORT` | `true` or `false` | Enable or disable PDF exports | | `CMD_CONFIG_FILE` | `/path/to/config.json` | optional override for the path to CodiMD's config file | +| `CMD_DB_URL` | `mysql://localhost:3306/database` | set the database URL | +| `CMD_LOGLEVEL` | `info`, `debug` ... | Defines what kind of logs are provided to stdout. | +| `CMD_FORBIDDEN_NOTE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` | +| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio` or `filesystem` | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md), also there's a whole section on their respective env vars below. | +| `CMD_SOURCE_URL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | + + +## CodiMD Location + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_DOMAIN` | `codimd.org` | domain name | -| `CMD_URL_PATH` | `codimd` | sub URL path, like `www.example.com/` | -| `CMD_HOST` | `localhost` | host to listen on | -| `CMD_PORT` | `80` | web app port | +| `CMD_URL_PATH` | `codimd` | If CodiMD is run from a subdirectory like `www.example.com/` | +| `CMD_HOST` | `localhost` | interface/ip to listen on | +| `CMD_PORT` | `80` | port to listen on | | `CMD_PATH` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `CMD_HOST` and `CMD_PORT` are ignored) | -| `CMD_LOGLEVEL` | `info` | Defines what kind of logs are provided to stdout. | -| `CMD_ALLOW_ORIGIN` | `localhost, codimd.org` | domain name whitelist (use comma to separate) | | `CMD_PROTOCOL_USESSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) | | `CMD_URL_ADDPORT` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) | -| `CMD_USECDN` | `true` or `false` | set to use CDN resources or not (default is `true`) | +| `CMD_ALLOW_ORIGIN` | `localhost, codimd.org` | domain name whitelist (use comma to separate) | + + +## CSP and HSTS + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_CSP_ENABLE` | `true` | whether to enable Content Security Policy (directives cannot be configured with environment variables) | +| `CMD_CSP_REPORTURI` | `https://.report-uri.com/r/d/csp/enforce` | Allows to add a URL for CSP reports in case of violations | +| `CMD_HSTS_ENABLE` | ` true` | set to enable [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) if HTTPS is also enabled (default is ` true`) | +| `CMD_HSTS_INCLUDE_SUBDOMAINS` | `true` | set to include subdomains in HSTS (default is `true`) | +| `CMD_HSTS_MAX_AGE` | `31536000` | max duration in seconds to tell clients to keep HSTS status (default is a year) | +| `CMD_HSTS_PRELOAD` | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) | + + +## Privacy and External Requests + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_ALLOW_GRAVATAR` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance | +| `CMD_USECDN` | `true` or `false` | set to use CDN resources or not| + + +## Users and Privileges + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_ALLOW_ANONYMOUS` | `true` or `false` | set to allow anonymous usage (default is `true`) | | `CMD_ALLOW_ANONYMOUS_EDITS` | `true` or `false` | if `allowAnonymous` is `true`, allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) | | `CMD_ALLOW_FREEURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL | -| `CMD_FORBIDDEN_NODE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` | | `CMD_DEFAULT_PERMISSION` | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) | -| `CMD_DB_URL` | `mysql://localhost:3306/database` | set the database URL | -| `CMD_SESSION_SECRET` | no example | Secret used to sign the session cookie. If non is set, one will randomly generated on startup | | `CMD_SESSION_LIFE` | `1209600000` | Session life time. (milliseconds) | +| `CMD_SESSION_SECRET` | no example | Secret used to sign the session cookie. If none is set, one will randomly generated on each startup, meaning all your users will be logged out. | + + +## Login methods + +### Email (local account) + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_EMAIL` | `true` or `false` | set to allow email signin | +| `CMD_ALLOW_EMAIL_REGISTER` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) | + + +### Dropbox Login + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_DROPBOX_CLIENTID` | no example | Dropbox API client id | +| `CMD_DROPBOX_CLIENTSECRET` | no example | Dropbox API client secret | + + +### Facebook Login + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_FACEBOOK_CLIENTID` | no example | Facebook API client id | | `CMD_FACEBOOK_CLIENTSECRET` | no example | Facebook API client secret | -| `CMD_TWITTER_CONSUMERKEY` | no example | Twitter API consumer key | -| `CMD_TWITTER_CONSUMERSECRET` | no example | Twitter API consumer secret | + + +### GitHub Login + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_GITHUB_CLIENTID` | no example | GitHub API client id | | `CMD_GITHUB_CLIENTSECRET` | no example | GitHub API client secret | + + +### GitLab Login + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_GITLAB_SCOPE` | `read_user` or `api` | GitLab API requested scope (default is `api`) (GitLab snippet import/export need `api` scope) | | `CMD_GITLAB_BASEURL` | no example | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) | | `CMD_GITLAB_CLIENTID` | no example | GitLab API client id | | `CMD_GITLAB_CLIENTSECRET` | no example | GitLab API client secret | | `CMD_GITLAB_VERSION` | no example | GitLab API version (v3 or v4) | -| `CMD_MATTERMOST_BASEURL` | no example | Mattermost authentication endpoint for versions below 5.0. For Mattermost version 5.0 and above, see [guide](docs/guides/auth/mattermost-self-hosted.md). | -| `CMD_MATTERMOST_CLIENTID` | no example | Mattermost API client id | -| `CMD_MATTERMOST_CLIENTSECRET` | no example | Mattermost API client secret | -| `CMD_DROPBOX_CLIENTID` | no example | Dropbox API client id | -| `CMD_DROPBOX_CLIENTSECRET` | no example | Dropbox API client secret | + + +### Google Login + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_GOOGLE_CLIENTID` | no example | Google API client id | | `CMD_GOOGLE_CLIENTSECRET` | no example | Google API client secret | + + +### LDAP Login + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_LDAP_URL` | `ldap://example.com` | URL of LDAP server | | `CMD_LDAP_BINDDN` | no example | bindDn for LDAP access | | `CMD_LDAP_BINDCREDENTIALS` | no example | bindCredentials for LDAP access | @@ -65,16 +150,21 @@ node-specific options you can configure this way. | `CMD_LDAP_USERNAMEFIELD` | Fallback to userid | The LDAP field which is used as the username on CodiMD | | `CMD_LDAP_TLS_CA` | `server-cert.pem, root.pem` | Root CA for LDAP TLS in PEM format (use comma to separate) | | `CMD_LDAP_PROVIDERNAME` | `My institution` | Optional name to be displayed at login form indicating the LDAP provider | -| `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](docs/guides/auth/saml-onelogin.md). | -| `CMD_SAML_IDPCERT` | `/path/to/cert.pem` | certificate file path of IdP in PEM format | -| `CMD_SAML_ISSUER` | no example | identity of the service provider (optional, default: serverurl)" | -| `CMD_SAML_IDENTIFIERFORMAT` | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) | -| `CMD_SAML_GROUPATTRIBUTE` | `memberOf` | attribute name for group list (optional) | -| `CMD_SAML_REQUIREDGROUPS` | `Hackmd-users` | group names that allowed (use vertical bar to separate) (optional) | -| `CMD_SAML_EXTERNALGROUPS` | `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) | -| `CMD_SAML_ATTRIBUTE_ID` | `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) | -| `CMD_SAML_ATTRIBUTE_USERNAME` | `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) | -| `CMD_SAML_ATTRIBUTE_EMAIL` | `mail` | attribute map for `email` (optional, default: NameID of SAML response if `CMD_SAML_IDENTIFIERFORMAT` is default) | + + +### Mattermost Login + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_MATTERMOST_BASEURL` | no example | Mattermost authentication endpoint for versions below 5.0. For Mattermost version 5.0 and above, see [guide](docs/guides/auth/mattermost-self-hosted.md). | +| `CMD_MATTERMOST_CLIENTID` | no example | Mattermost API client id | +| `CMD_MATTERMOST_CLIENTSECRET` | no example | Mattermost API client secret | + + +### OAuth2 Login + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_OAUTH2_USER_PROFILE_URL` | `https://example.com` | where retrieve information about a user after succesful login. Needs to output JSON. (no default value) Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details on all of the `CMD_OAUTH2...` options. | | `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR` | `name` | where to find the username in the JSON from the user profile URL. (no default value)| | `CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR` | `display-name` | where to find the display-name in the JSON from the user profile URL. (no default value) | @@ -84,30 +174,74 @@ node-specific options you can configure this way. | `CMD_OAUTH2_CLIENT_ID` | `afae02fckafd...` | you will get this from your OAuth2 provider when you register CodiMD as OAuth2-client, (no default value) | | `CMD_OAUTH2_CLIENT_SECRET` | `afae02fckafd...` | you will get this from your OAuth2 provider when you register CodiMD as OAuth2-client, (no default value) | | `CMD_OAUTH2_PROVIDERNAME` | `My institution` | Optional name to be displayed at login form indicating the oAuth2 provider | -| `CMD_IMGUR_CLIENTID` | no example | Imgur API client id | -| `CMD_EMAIL` | `true` or `false` | set to allow email signin | -| `CMD_ALLOW_PDF_EXPORT` | `true` or `false` | Enable or disable PDF exports | -| `CMD_ALLOW_EMAIL_REGISTER` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) | -| `CMD_ALLOW_GRAVATAR` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance | -| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio` or `filesystem` | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md) | + + +### SAML Login + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](docs/guides/auth/saml-onelogin.md). | +| `CMD_SAML_IDPCERT` | `/path/to/cert.pem` | certificate file path of IdP in PEM format | +| `CMD_SAML_ISSUER` | no example | identity of the service provider (optional, default: serverurl)" | +| `CMD_SAML_IDENTIFIERFORMAT` | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) | +| `CMD_SAML_GROUPATTRIBUTE` | `memberOf` | attribute name for group list (optional) | +| `CMD_SAML_REQUIREDGROUPS` | `codimd-users` | group names that allowed (use vertical bar to separate) (optional) | +| `CMD_SAML_EXTERNALGROUPS` | `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) | +| `CMD_SAML_ATTRIBUTE_ID` | `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) | +| `CMD_SAML_ATTRIBUTE_USERNAME` | `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) | +| `CMD_SAML_ATTRIBUTE_EMAIL` | `mail` | attribute map for `email` (optional, default: NameID of SAML response if `CMD_SAML_IDENTIFIERFORMAT` is default) | + + +### Twitter Login + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_TWITTER_CONSUMERKEY` | no example | Twitter API consumer key | +| `CMD_TWITTER_CONSUMERSECRET` | no example | Twitter API consumer secret | + + +## Upload Storage + +These are only relevant when they are also configured in sync with their +`CMD_IMAGE_UPLOAD_TYPE`. Also keep in mind, that `filesystem` is available, so +you don't have to use either of these. + + +### Amazon S3 + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_S3_ACCESS_KEY_ID` | no example | AWS access key id | | `CMD_S3_SECRET_ACCESS_KEY` | no example | AWS secret key | | `CMD_S3_REGION` | `ap-northeast-1` | AWS S3 region | | `CMD_S3_BUCKET` | no example | AWS S3 bucket name | + + +### Azure Blob Storage + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_AZURE_CONNECTION_STRING` | no example | Azure Blob Storage connection string | +| `CMD_AZURE_CONTAINER` | no example | Azure Blob Storage container name (automatically created if non existent) | + + +### imgur + +| variable | example value | description | +| -------- | ------------- | ----------- | +| `CMD_IMGUR_CLIENTID` | no example | Imgur API client id | + + +### Minio + +| variable | example value | description | +| -------- | ------------- | ----------- | | `CMD_MINIO_ACCESS_KEY` | no example | Minio access key | | `CMD_MINIO_SECRET_KEY` | no example | Minio secret key | | `CMD_MINIO_ENDPOINT` | `minio.example.org` | Address of your Minio endpoint/instance | | `CMD_MINIO_PORT` | `9000` | Port that is used for your Minio instance | | `CMD_MINIO_SECURE` | `true` | If set to `true` HTTPS is used for Minio | -| `CMD_AZURE_CONNECTION_STRING` | no example | Azure Blob Storage connection string | -| `CMD_AZURE_CONTAINER` | no example | Azure Blob Storage container name (automatically created if non existent) | -| `CMD_HSTS_ENABLE` | ` true` | set to enable [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) if HTTPS is also enabled (default is ` true`) | -| `CMD_HSTS_INCLUDE_SUBDOMAINS` | `true` | set to include subdomains in HSTS (default is `true`) | -| `CMD_HSTS_MAX_AGE` | `31536000` | max duration in seconds to tell clients to keep HSTS status (default is a year) | -| `CMD_HSTS_PRELOAD` | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) | -| `CMD_CSP_ENABLE` | `true` | whether to enable Content Security Policy (directives cannot be configured with environment variables) | -| `CMD_CSP_REPORTURI` | `https://.report-uri.com/r/d/csp/enforce` | Allows to add a URL for CSP reports in case of violations | -| `CMD_SOURCE_URL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | + **Note:** *Due to the rename process we renamed all `HMD_`-prefix variables to be `CMD_`-prefixed. The old ones continue to work.* From ba6ede57bd32aae79bbcf41964777374620aeab0 Mon Sep 17 00:00:00 2001 From: Claudius Date: Mon, 1 Apr 2019 01:26:36 +0200 Subject: [PATCH 19/36] moving code of conduct into its own file for better discoverability Signed-off-by: Claudius --- CODE_OF_CONDUCT.md | 37 +++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 42 +++--------------------------------------- 2 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f15b5af --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,37 @@ +Contributor Code of Conduct +=== + +As contributors and maintainers of this project, and in the interest of fostering an open and +welcoming community, we pledge to respect all people who contribute through reporting issues, +posting feature requests, updating documentation, submitting pull requests or patches, and other +activities. + +We are committed to making participation in this project a harassment-free experience for everyone, +regardless of level of experience, gender, gender identity and expression, sexual orientation, +disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic addresses, without explicit + permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, +code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By +adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently +applying these principles to every aspect of managing this project. Project maintainers who do not +follow or enforce the Code of Conduct may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces when an individual is +representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an +issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), +version 1.2.0, available at +[http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40f7355..0522874 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,8 @@ When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. -Please note we have a code of conduct, please follow it in all your interactions with the project. +Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your +interactions with the project. ## Pull Request Process 1. Ensure you signed all your commits with Developer Certificate of Origin (DCO). @@ -16,44 +17,7 @@ Please note we have a code of conduct, please follow it in all your interactions 5. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. -## Contributor Code of Conduct - -As contributors and maintainers of this project, and in the interest of fostering an open and -welcoming community, we pledge to respect all people who contribute through reporting issues, -posting feature requests, updating documentation, submitting pull requests or patches, and other -activities. - -We are committed to making participation in this project a harassment-free experience for everyone, -regardless of level of experience, gender, gender identity and expression, sexual orientation, -disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing other's private information, such as physical or electronic addresses, without explicit - permission -* Other unethical or unprofessional conduct. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, -code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By -adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently -applying these principles to every aspect of managing this project. Project maintainers who do not -follow or enforce the Code of Conduct may be permanently removed from the project team. - -This code of conduct applies both within project spaces and in public spaces when an individual is -representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an -issue or contacting one or more of the project maintainers. - -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), -version 1.2.0, available at -[http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) - -### Sign your work +## Sign your work We use the Developer Certificate of Origin (DCO) as a additional safeguard for the CodiMD project. This is a well established and widely used From a140bff47e02597738b9933db00c88b2b3596864 Mon Sep 17 00:00:00 2001 From: Claudius Date: Mon, 1 Apr 2019 09:42:33 +0200 Subject: [PATCH 20/36] minor fixes to internal links in documentation --- docs/configuration-config-file.md | 12 ++++++------ docs/configuration-env-vars.md | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/configuration-config-file.md b/docs/configuration-config-file.md index 41122aa..ca02794 100644 --- a/docs/configuration-config-file.md +++ b/docs/configuration-config-file.md @@ -3,9 +3,9 @@ Configuration Using Config file You can choose to configure CodiMD with either a config file or with [environment variables](configuration-env-vars.md). The config file is processed -in [`lib/config/index.js`](lib/config/index.js) - so this is the first +in [`lib/config/index.js`](../lib/config/index.js) - so this is the first place to look if anything is missing not obvious from this document. The -default values are defined in [`lib/config/default.js`](lib/config/default.js), +default values are defined in [`lib/config/default.js`](../lib/config/default.js), in case you wonder if you even need to override it. Environment variables take precedence over configurations from the config files. @@ -29,7 +29,7 @@ to `config.json` before filling in your own details. | `dbURL` | `mysql://localhost:3306/database` | set the db URL; if set, then db config (below) won't be applied | | `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` | | `loglevel` | `info` | Defines what kind of logs are provided to stdout. | -| `imageUploadType` | `imgur`, `s3`, `minio`, `azure` or `filesystem`(default) | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md)| +| `imageUploadType` | `imgur`, `s3`, `minio`, `azure` or `filesystem`(default) | Where to upload images. For S3, see our Image Upload Guides for [S3](guides/s3-image-upload.md) or [Minio](guides/minio-image-upload.md)| | `sourceURL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | | `staticCacheTime` | `1 * 24 * 60 * 60 * 1000` | static file cache time | | `heartbeatInterval` | `5000` | socket.io heartbeat interval | @@ -118,7 +118,7 @@ Most of these have never been documented for the config.json, feel free to expan | variables | example values | description | | --------- | ------ | ----------- | -| `oauth2` | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details!| +| `oauth2` | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](guides/auth/mattermost-self-hosted.md) or [Nextcloud](guides/auth/nextcloud.md) examples for more details!| ### SAML Login ### Twitter Login @@ -133,7 +133,7 @@ Most of these have never been documented for the config.json, feel free to expan | variables | example values | description | | --------- | ------ | ----------- | -| `s3` | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageuploadtype` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) | +| `s3` | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageuploadtype` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](guides/s3-image-upload.md) | | `s3bucket` | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` or `minio` | ### Azure Blob Storage @@ -142,6 +142,6 @@ Most of these have never been documented for the config.json, feel free to expan | variables | example values | description | | --------- | ------ | ----------- | -| `minio` | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also checkout our [Minio Image Upload Guide](docs/guides/minio-image-upload.md) | +| `minio` | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also check out our [Minio Image Upload Guide](guides/minio-image-upload.md) | 1: relative paths are based on CodiMD's base directory diff --git a/docs/configuration-env-vars.md b/docs/configuration-env-vars.md index 5510736..c81deab 100644 --- a/docs/configuration-env-vars.md +++ b/docs/configuration-env-vars.md @@ -4,9 +4,9 @@ Configuration Using Environment variables You can choose to configure CodiMD with either a [config file](configuration-config-file.md) or with environment variables. Environment variables are processed in -[`lib/config/environment.js`](lib/config/environment.js) - so this is the first +[`lib/config/environment.js`](../lib/config/environment.js) - so this is the first place to look if anything is missing not obvious from this document. The -default values are defined in [`lib/config/default.js`](lib/config/default.js), +default values are defined in [`lib/config/default.js`](../lib/config/default.js), in case you wonder if you even need to override it. Environment variables take precedence over configurations from the config files. @@ -33,7 +33,7 @@ defaultNotePath can't be set from env-vars | `CMD_DB_URL` | `mysql://localhost:3306/database` | set the database URL | | `CMD_LOGLEVEL` | `info`, `debug` ... | Defines what kind of logs are provided to stdout. | | `CMD_FORBIDDEN_NOTE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` | -| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio` or `filesystem` | Where to upload images. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md), also there's a whole section on their respective env vars below. | +| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio` or `filesystem` | Where to upload images. For S3, see our Image Upload Guides for [S3](guides/s3-image-upload.md) or [Minio](guides/minio-image-upload.md), also there's a whole section on their respective env vars below. | | `CMD_SOURCE_URL` | `https://github.com/codimd/server/tree/` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) | @@ -156,7 +156,7 @@ defaultNotePath can't be set from env-vars | variable | example value | description | | -------- | ------------- | ----------- | -| `CMD_MATTERMOST_BASEURL` | no example | Mattermost authentication endpoint for versions below 5.0. For Mattermost version 5.0 and above, see [guide](docs/guides/auth/mattermost-self-hosted.md). | +| `CMD_MATTERMOST_BASEURL` | no example | Mattermost authentication endpoint for versions below 5.0. For Mattermost version 5.0 and above, see [guide](guides/auth/mattermost-self-hosted.md). | | `CMD_MATTERMOST_CLIENTID` | no example | Mattermost API client id | | `CMD_MATTERMOST_CLIENTSECRET` | no example | Mattermost API client secret | @@ -165,7 +165,7 @@ defaultNotePath can't be set from env-vars | variable | example value | description | | -------- | ------------- | ----------- | -| `CMD_OAUTH2_USER_PROFILE_URL` | `https://example.com` | where retrieve information about a user after succesful login. Needs to output JSON. (no default value) Refer to the [Mattermost](docs/guides/auth/mattermost-self-hosted.md) or [Nextcloud](docs/guides/auth/nextcloud.md) examples for more details on all of the `CMD_OAUTH2...` options. | +| `CMD_OAUTH2_USER_PROFILE_URL` | `https://example.com` | where retrieve information about a user after succesful login. Needs to output JSON. (no default value) Refer to the [Mattermost](guides/auth/mattermost-self-hosted.md) or [Nextcloud](guides/auth/nextcloud.md) examples for more details on all of the `CMD_OAUTH2...` options. | | `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR` | `name` | where to find the username in the JSON from the user profile URL. (no default value)| | `CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR` | `display-name` | where to find the display-name in the JSON from the user profile URL. (no default value) | | `CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR` | `email` | where to find the email address in the JSON from the user profile URL. (no default value) | @@ -180,7 +180,7 @@ defaultNotePath can't be set from env-vars | variable | example value | description | | -------- | ------------- | ----------- | -| `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](docs/guides/auth/saml-onelogin.md). | +| `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](guides/auth/saml-onelogin.md). | | `CMD_SAML_IDPCERT` | `/path/to/cert.pem` | certificate file path of IdP in PEM format | | `CMD_SAML_ISSUER` | no example | identity of the service provider (optional, default: serverurl)" | | `CMD_SAML_IDENTIFIERFORMAT` | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) | From 5951dd1805d8c74ac1da355baa89343838f38305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20H=C3=BCttemann?= Date: Mon, 1 Apr 2019 08:27:30 +0100 Subject: [PATCH 21/36] Add missing space to footer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Henrik "HerHde" Hüttemann --- public/views/index/body.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs index db22f2e..bb0f04a 100644 --- a/public/views/index/body.ejs +++ b/public/views/index/body.ejs @@ -151,7 +151,7 @@

- <%- __('Powered by %s', 'CodiMD') %> | <%= __('Releases') %>| <%= __('Source Code') %><% if(privacyStatement) { %> | <%= __('Privacy') %><% } %><% if(termsOfUse) { %> | <%= __('Terms of Use') %><% } %> + <%- __('Powered by %s', 'CodiMD') %> | <%= __('Releases') %> | <%= __('Source Code') %><% if(privacyStatement) { %> | <%= __('Privacy') %><% } %><% if(termsOfUse) { %> | <%= __('Terms of Use') %><% } %>