Compare commits
5 Commits
normalize-
...
v1.6.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95f7b7b8e8 | ||
|
|
1dbd038aef | ||
|
|
4f7e2f636c | ||
|
|
eb0a7f9d84 | ||
|
|
02370b30d9 |
@@ -1,30 +1,8 @@
|
|||||||
# Save as .codeclimate.yml (note leading .) in project root directory
|
# Save as .codeclimate.yml (note leading .) in project root directory
|
||||||
version: "2"
|
|
||||||
languages:
|
languages:
|
||||||
Ruby: false
|
Ruby: true
|
||||||
JavaScript: true
|
JavaScript: true
|
||||||
PHP: false
|
PHP: true
|
||||||
checks:
|
|
||||||
file-lines:
|
|
||||||
config:
|
|
||||||
threshold: 500
|
|
||||||
method-lines:
|
|
||||||
config:
|
|
||||||
threshold: 75
|
|
||||||
method-complexity:
|
|
||||||
config:
|
|
||||||
threshold: 10
|
|
||||||
similar-code:
|
|
||||||
config:
|
|
||||||
threshold: 65
|
|
||||||
plugins:
|
|
||||||
duplication:
|
|
||||||
enabled: true
|
|
||||||
config:
|
|
||||||
languages:
|
|
||||||
javascript:
|
|
||||||
mass_threshold: 110
|
|
||||||
count_threshold: 3
|
|
||||||
exclude_paths:
|
exclude_paths:
|
||||||
- "public/vendor/*"
|
- "public/vendor/*"
|
||||||
- "test/*"
|
- "test/*"
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
.*
|
|
||||||
logs
|
|
||||||
test
|
|
||||||
node_modules
|
|
||||||
commitlint.config.js
|
|
||||||
nodebb.bat
|
|
||||||
renovate.json
|
|
||||||
*.yml
|
|
||||||
*.md
|
|
||||||
Dockerfile
|
|
||||||
@@ -7,6 +7,7 @@ node_modules/
|
|||||||
logs/
|
logs/
|
||||||
/public/templates
|
/public/templates
|
||||||
/public/uploads
|
/public/uploads
|
||||||
|
/public/sounds
|
||||||
/public/vendor
|
/public/vendor
|
||||||
/public/src/modules/string.js
|
/public/src/modules/string.js
|
||||||
.idea/
|
.idea/
|
||||||
@@ -17,5 +18,3 @@ logs/
|
|||||||
/build
|
/build
|
||||||
.eslintrc
|
.eslintrc
|
||||||
test/files
|
test/files
|
||||||
*.min.js
|
|
||||||
install/docker/
|
|
||||||
131
.eslintrc
@@ -1,3 +1,132 @@
|
|||||||
{
|
{
|
||||||
"extends": "nodebb"
|
"extends": "airbnb-base",
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "script"
|
||||||
|
},
|
||||||
|
|
||||||
|
"rules": {
|
||||||
|
// Customized
|
||||||
|
"handle-callback-err": [ "error","^(e$|(e|(.*(_e|E)))rr)" ],
|
||||||
|
"comma-dangle": ["error", {
|
||||||
|
"arrays": "always-multiline",
|
||||||
|
"objects": "always-multiline",
|
||||||
|
"imports": "always-multiline",
|
||||||
|
"exports": "always-multiline",
|
||||||
|
"functions": "never"
|
||||||
|
}],
|
||||||
|
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"newline-per-chained-call": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-mixed-operators": ["error", { "allowSamePrecedence": true }],
|
||||||
|
"strict": ["error", "global"],
|
||||||
|
"consistent-return": "off",
|
||||||
|
"func-names": "off",
|
||||||
|
"no-tabs": "off",
|
||||||
|
"indent": ["error", "tab"],
|
||||||
|
"no-eq-null": "off",
|
||||||
|
"camelcase": "off",
|
||||||
|
"no-new": "off",
|
||||||
|
"no-shadow": "off",
|
||||||
|
"no-use-before-define": ["error", "nofunc"],
|
||||||
|
"no-prototype-builtins": "off",
|
||||||
|
"new-cap": "off",
|
||||||
|
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
|
||||||
|
"object-curly-newline": "off",
|
||||||
|
"no-restricted-globals": "off",
|
||||||
|
"function-paren-newline": "off",
|
||||||
|
"import/no-unresolved": "error",
|
||||||
|
|
||||||
|
// ES6
|
||||||
|
"prefer-rest-params": "off",
|
||||||
|
"prefer-spread": "off",
|
||||||
|
"prefer-arrow-callback": "off",
|
||||||
|
"prefer-template": "off",
|
||||||
|
"no-var": "off",
|
||||||
|
"object-shorthand": "off",
|
||||||
|
"vars-on-top": "off",
|
||||||
|
"prefer-destructuring": "off",
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
"import/no-extraneous-dependencies": "off",
|
||||||
|
"import/no-dynamic-require": "off",
|
||||||
|
"import/newline-after-import": "off",
|
||||||
|
"no-bitwise": "off",
|
||||||
|
"global-require": "off",
|
||||||
|
"max-len": "off",
|
||||||
|
"no-param-reassign": "off",
|
||||||
|
"no-restricted-syntax": "off",
|
||||||
|
"no-script-url": "off",
|
||||||
|
"default-case": "off",
|
||||||
|
"linebreak-style": "off",
|
||||||
|
|
||||||
|
// "no-multi-assign": "off",
|
||||||
|
// "one-var": "off",
|
||||||
|
// "no-undef": "off",
|
||||||
|
// "max-nested-callbacks": "off",
|
||||||
|
// "no-mixed-requires": "off",
|
||||||
|
// "brace-style": "off",
|
||||||
|
// "max-statements-per-line": "off",
|
||||||
|
// "no-unused-vars": "off",
|
||||||
|
// "no-mixed-spaces-and-tabs": "off",
|
||||||
|
// "no-useless-concat": "off",
|
||||||
|
// "require-jsdoc": "off",
|
||||||
|
// "eqeqeq": "off",
|
||||||
|
// "no-negated-condition": "off",
|
||||||
|
// "one-var-declaration-per-line": "off",
|
||||||
|
// "no-lonely-if": "off",
|
||||||
|
// "radix": "off",
|
||||||
|
// "no-else-return": "off",
|
||||||
|
// "no-useless-escape": "off",
|
||||||
|
// "block-scoped-var": "off",
|
||||||
|
// "operator-assignment": "off",
|
||||||
|
// "yoda": "off",
|
||||||
|
// "no-loop-func": "off",
|
||||||
|
// "no-void": "off",
|
||||||
|
// "valid-jsdoc": "off",
|
||||||
|
// "no-cond-assign": "off",
|
||||||
|
// "no-redeclare": "off",
|
||||||
|
// "no-unreachable": "off",
|
||||||
|
// "no-nested-ternary": "off",
|
||||||
|
// "operator-linebreak": "off",
|
||||||
|
// "guard-for-in": "off",
|
||||||
|
// "no-unneeded-ternary": "off",
|
||||||
|
// "no-sequences": "off",
|
||||||
|
// "no-extend-native": "off",
|
||||||
|
// "no-shadow-restricted-names": "off",
|
||||||
|
// "no-extra-boolean-cast": "off",
|
||||||
|
// "no-path-concat": "off",
|
||||||
|
// "no-unused-expressions": "off",
|
||||||
|
// "no-return-assign": "off",
|
||||||
|
// "no-restricted-modules": "off",
|
||||||
|
// "object-curly-spacing": "off",
|
||||||
|
// "indent": "off",
|
||||||
|
// "padded-blocks": "off",
|
||||||
|
// "eol-last": "off",
|
||||||
|
// "lines-around-directive": "off",
|
||||||
|
// "strict": "off",
|
||||||
|
// "comma-dangle": "off",
|
||||||
|
// "no-multi-spaces": "off",
|
||||||
|
// "quotes": "off",
|
||||||
|
// "keyword-spacing": "off",
|
||||||
|
// "no-mixed-operators": "off",
|
||||||
|
// "comma-spacing": "off",
|
||||||
|
// "no-trailing-spaces": "off",
|
||||||
|
// "key-spacing": "off",
|
||||||
|
// "no-multiple-empty-lines": "off",
|
||||||
|
// "spaced-comment": "off",
|
||||||
|
// "space-in-parens": "off",
|
||||||
|
// "block-spacing": "off",
|
||||||
|
// "quote-props": "off",
|
||||||
|
// "space-unary-ops": "off",
|
||||||
|
// "no-empty": "off",
|
||||||
|
// "dot-notation": "off",
|
||||||
|
// "func-call-spacing": "off",
|
||||||
|
// "array-bracket-spacing": "off",
|
||||||
|
// "object-property-newline": "off",
|
||||||
|
// "no-continue": "off",
|
||||||
|
// "no-extra-semi": "off",
|
||||||
|
// "no-spaced-func": "off",
|
||||||
|
// "no-useless-return": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
== Github Issues are for bug reports and feature requests only ==
|
||||||
|
== Please visit https://community.nodebb.org for other support ==
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- ++ Please include the following information when submitting a bug report ++ -->
|
||||||
|
|
||||||
|
- **NodeBB version:**
|
||||||
|
- **NodeBB git hash:**
|
||||||
|
<!-- (to find your git hash, execute `git rev-parse HEAD` from the main NodeBB directory) -->
|
||||||
|
- **Database type:** mongo or redis
|
||||||
|
- **Database version:**
|
||||||
|
<!-- `mongod --version` or `redis-server --version` -->
|
||||||
|
- **Exact steps to cause this issue:**
|
||||||
|
<!--
|
||||||
|
1. First I did this...
|
||||||
|
2. Then, I clicked on this item...
|
||||||
|
-->
|
||||||
|
- **What you expected:**
|
||||||
|
<!-- e.g. I expected *abc* to *xyz* -->
|
||||||
|
- **What happened instead:**
|
||||||
|
<!-- e.g. Instead, I got *zyx* and NodeBB set fire to my house -->
|
||||||
|
|
||||||
|
<!-- Thank you! -->
|
||||||
81
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,81 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: File a bug report
|
|
||||||
labels: ["bug"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Github Issues are for bug reports and feature requests only
|
|
||||||
Please visit https://community.nodebb.org for other support
|
|
||||||
Found a security exploit? Please email us at security@nodebb.org instead for immediate attention
|
|
||||||
### → DO NOT SUBMIT VULNERABILITIES TO THE PUBLIC BUG TRACKER ←
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: NodeBB version
|
|
||||||
placeholder: e.g. v2.2.0
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: NodeBB git hash
|
|
||||||
description: to find your git hash, execute `git rev-parse HEAD` from the main NodeBB directory
|
|
||||||
placeholder: e.g. 783250ee6f8c51cdc243ce3b8d9f1a080517247e
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: NodeJS version
|
|
||||||
placeholder: e.g. v16.15.1
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Installed NodeBB plugins
|
|
||||||
description: to find installed plugins run `./nodebb plugins`
|
|
||||||
placeholder: |
|
|
||||||
e.g.
|
|
||||||
* nodebb-plugin-2factor@5.0.1 (installed, disabled)
|
|
||||||
* nodebb-plugin-composer-default@8.0.0 (installed, enabled)
|
|
||||||
* nodebb-plugin-dbsearch@5.1.5 (installed, disabled)
|
|
||||||
* nodebb-plugin-emoji@4.0.4 (installed, enabled)
|
|
||||||
* nodebb-plugin-emoji-android@3.0.0 (installed, enabled)
|
|
||||||
* nodebb-plugin-markdown@10.0.0 (installed, enabled)
|
|
||||||
* nodebb-plugin-mentions@3.0.11 (installed, enabled)
|
|
||||||
* nodebb-plugin-spam-be-gone@1.0.0 (installed, disabled)
|
|
||||||
* nodebb-rewards-essentials@0.2.1 (installed, enabled)
|
|
||||||
* nodebb-theme-lavender@6.0.0 (installed, disabled)
|
|
||||||
* nodebb-theme-persona@12.0.11 (installed, enabled)
|
|
||||||
* nodebb-theme-slick@2.0.2 (installed, disabled)
|
|
||||||
* nodebb-theme-vanilla@12.1.18 (installed, disabled)
|
|
||||||
* nodebb-widget-essentials@6.0.0 (installed, enabled)
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: Database type
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- MongoDB
|
|
||||||
- Redis
|
|
||||||
- PostgreSQL
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: Database version
|
|
||||||
description: "`mongod --version`, `redis-server --version`, or `postgres --version`"
|
|
||||||
placeholder: e.g. v5.0.9
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Exact steps to cause this issue
|
|
||||||
placeholder: |
|
|
||||||
1. First I did this...
|
|
||||||
2. Then, I clicked on this item...
|
|
||||||
|
|
||||||
A quick note: MP4 and MOV formatted video files are now allowed to be uploaded to GH.
|
|
||||||
Please upload if reproduction steps are hard to describe or reproduce reliably.
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: What you expected
|
|
||||||
placeholder: e.g. I expected *abc* to *xyz*
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: What happened instead
|
|
||||||
placeholder: e.g. Instead, I got *zyx* and NodeBB set fire to my house
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Anything else?
|
|
||||||
description: Any additional context about the issue you're encountering
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: "**Thank you!**"
|
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: true
|
|
||||||
contact_links:
|
|
||||||
- name: Community Forum
|
|
||||||
url: https://community.nodebb.org
|
|
||||||
about: Github Issues are for bug reports and feature requests only, please use community forum for other support
|
|
||||||
24
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,24 +0,0 @@
|
|||||||
name: Feature Request
|
|
||||||
description: Suggest a new feature
|
|
||||||
labels: ["enhancement"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Github Issues are for bug reports and feature requests only
|
|
||||||
Please visit https://community.nodebb.org for other support
|
|
||||||
Found a security exploit? Please email us at security@nodebb.org instead for immediate attention
|
|
||||||
### → DO NOT SUBMIT VULNERABILITIES TO THE PUBLIC BUG TRACKER ←
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: Community forum reference
|
|
||||||
description: If this feature was already discussed on the Community Forum link it here
|
|
||||||
placeholder: https://community.nodebb.org/topic/0/example-feature-request
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: "**Thank you!**"
|
|
||||||
9
.github/SECURITY.md
vendored
@@ -1,9 +0,0 @@
|
|||||||
# Reporting a security vulnerability
|
|
||||||
|
|
||||||
NodeBB's security policy is based around a private bug bounty program. Users are invited to explore NodeBB for vulnerabilities, and report them to the NodeBB team so that they can be patched.
|
|
||||||
|
|
||||||
If you have found a security vulnerability, **do not post it onto our GitHub tracker**. Some security vulnerabilities are quite severe and discretion is recommended. Email the NodeBB Security Team at security@nodebb.org, instead, even if you are not sure whether something qualifies.
|
|
||||||
|
|
||||||
# Bug Bounty Program
|
|
||||||
|
|
||||||
Security vulnerability reports may be eligible for a bounty based on severity and confirmation from NodeBB team members. For full details regarding our bug bounty program, including the bounty amounts, please consult the [dedicated page for our Bug Bounty Program](https://nodebb.org/bounty).
|
|
||||||
72
.github/workflows/docker.yml
vendored
@@ -1,72 +0,0 @@
|
|||||||
name: Run Docker
|
|
||||||
|
|
||||||
# Controls when the workflow will run
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'master'
|
|
||||||
- 'v*.x'
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Get current date in NST
|
|
||||||
run: echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: ghcr.io/${{ github.repository }}
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
type=semver,pattern={{major}}.x
|
|
||||||
type=raw,value=latest,enable={{is_default_branch}}
|
|
||||||
type=ref,event=branch,enable=${{ github.event.repository.default_branch != github.ref }}
|
|
||||||
type=raw,value=${{ env.CURRENT_DATE_NST }}
|
|
||||||
flavor: |
|
|
||||||
latest=true
|
|
||||||
|
|
||||||
- name: Cache node_modules
|
|
||||||
id: cache-node-modules
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: var-cache-node-modules
|
|
||||||
key: var-cache-node-modules-${{ hashFiles('Dockerfile', 'install/package.json') }}
|
|
||||||
|
|
||||||
- name: Build and push Docker images
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=min
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
212
.github/workflows/test.yaml
vendored
@@ -1,212 +0,0 @@
|
|||||||
name: Lint and test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
permissions:
|
|
||||||
checks: write # for coverallsapp/github-action to create new checks
|
|
||||||
contents: read # for actions/checkout to fetch code
|
|
||||||
name: Lint and test
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest]
|
|
||||||
node: [18, 20]
|
|
||||||
database: [mongo-dev, mongo, redis, postgres]
|
|
||||||
include:
|
|
||||||
# only run coverage once
|
|
||||||
- os: ubuntu-latest
|
|
||||||
node: 18
|
|
||||||
coverage: true
|
|
||||||
# test under development once
|
|
||||||
- database: mongo-dev
|
|
||||||
test_env: development
|
|
||||||
# only run eslint once
|
|
||||||
- os: ubuntu-latest
|
|
||||||
node: 18
|
|
||||||
database: mongo-dev
|
|
||||||
lint: true
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
env:
|
|
||||||
TEST_ENV: ${{ matrix.test_env || 'production' }}
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: 'postgres:17-alpine'
|
|
||||||
env:
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
# Set health checks to wait until postgres has started
|
|
||||||
options: >-
|
|
||||||
--health-cmd pg_isready
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
--health-retries 5
|
|
||||||
ports:
|
|
||||||
# Maps port 5432 on service container to the host
|
|
||||||
- 5432:5432
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: 'redis:7.4.2'
|
|
||||||
# Set health checks to wait until redis has started
|
|
||||||
options: >-
|
|
||||||
--health-cmd "redis-cli ping"
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
--health-retries 5
|
|
||||||
ports:
|
|
||||||
# Maps port 6379 on service container to the host
|
|
||||||
- 6379:6379
|
|
||||||
|
|
||||||
mongo:
|
|
||||||
image: 'mongo:8.0'
|
|
||||||
ports:
|
|
||||||
# Maps port 27017 on service container to the host
|
|
||||||
- 27017:27017
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- run: cp install/package.json package.json
|
|
||||||
|
|
||||||
- name: Install Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node }}
|
|
||||||
|
|
||||||
- name: NPM Install
|
|
||||||
uses: bahmutov/npm-install@v1
|
|
||||||
with:
|
|
||||||
useLockFile: false
|
|
||||||
|
|
||||||
- name: Setup on MongoDB
|
|
||||||
if: startsWith(matrix.database, 'mongo')
|
|
||||||
env:
|
|
||||||
SETUP: >-
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4567",
|
|
||||||
"secret": "abcdef",
|
|
||||||
"admin:username": "admin",
|
|
||||||
"admin:email": "test@example.org",
|
|
||||||
"admin:password": "hAN3Eg8W",
|
|
||||||
"admin:password:confirm": "hAN3Eg8W",
|
|
||||||
|
|
||||||
"database": "mongo",
|
|
||||||
"mongo:host": "127.0.0.1",
|
|
||||||
"mongo:port": 27017,
|
|
||||||
"mongo:username": "",
|
|
||||||
"mongo:password": "",
|
|
||||||
"mongo:database": "nodebb"
|
|
||||||
}
|
|
||||||
CI: >-
|
|
||||||
{
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": 27017,
|
|
||||||
"database": "ci_test"
|
|
||||||
}
|
|
||||||
run: |
|
|
||||||
node app --setup="${SETUP}" --ci="${CI}"
|
|
||||||
|
|
||||||
- name: Setup on PostgreSQL
|
|
||||||
if: startsWith(matrix.database, 'postgres')
|
|
||||||
env:
|
|
||||||
SETUP: >-
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4567",
|
|
||||||
"secret": "abcdef",
|
|
||||||
"admin:username": "admin",
|
|
||||||
"admin:email": "test@example.org",
|
|
||||||
"admin:password": "hAN3Eg8W",
|
|
||||||
"admin:password:confirm": "hAN3Eg8W",
|
|
||||||
|
|
||||||
"database": "postgres",
|
|
||||||
"postgres:host": "127.0.0.1",
|
|
||||||
"postgres:port": 5432,
|
|
||||||
"postgres:username": "postgres",
|
|
||||||
"postgres:password": "postgres",
|
|
||||||
"postgres:database": "nodebb"
|
|
||||||
}
|
|
||||||
CI: >-
|
|
||||||
{
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"database": "ci_test",
|
|
||||||
"port": 5432,
|
|
||||||
"username": "postgres",
|
|
||||||
"password": "postgres"
|
|
||||||
}
|
|
||||||
run: |
|
|
||||||
node -e "const { Client } = require('pg'); const c = new Client({ host: '127.0.0.1', port: 5432, user: 'postgres', password: 'postgres' }); c.connect().then(() => c.query('CREATE DATABASE nodebb')).then(() => c.query('CREATE DATABASE ci_test')).then(() => c.end())"
|
|
||||||
node app --setup="${SETUP}" --ci="${CI}"
|
|
||||||
|
|
||||||
- name: Setup on Redis
|
|
||||||
if: startsWith(matrix.database, 'redis')
|
|
||||||
env:
|
|
||||||
SETUP: >-
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:4567/forum",
|
|
||||||
"secret": "abcdef",
|
|
||||||
"admin:username": "admin",
|
|
||||||
"admin:email": "test@example.org",
|
|
||||||
"admin:password": "hAN3Eg8W",
|
|
||||||
"admin:password:confirm": "hAN3Eg8W",
|
|
||||||
|
|
||||||
"database": "redis",
|
|
||||||
"redis:host": "127.0.0.1",
|
|
||||||
"redis:port": 6379,
|
|
||||||
"redis:password": "",
|
|
||||||
"redis:database": 0
|
|
||||||
}
|
|
||||||
CI: >-
|
|
||||||
{
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"database": 1,
|
|
||||||
"port": 6379
|
|
||||||
}
|
|
||||||
run: |
|
|
||||||
node app --setup="${SETUP}" --ci="${CI}"
|
|
||||||
|
|
||||||
- name: Run ESLint
|
|
||||||
if: matrix.lint
|
|
||||||
run: npm run lint
|
|
||||||
|
|
||||||
- name: Node tests
|
|
||||||
run: npm test
|
|
||||||
|
|
||||||
- name: Extract coverage info
|
|
||||||
run: npm run coverage
|
|
||||||
|
|
||||||
- name: Test coverage
|
|
||||||
uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6
|
|
||||||
if: matrix.coverage
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
flag-name: ${{ matrix.os }}-node-${{ matrix.node }}-db-${{ matrix.database }}
|
|
||||||
parallel: true
|
|
||||||
|
|
||||||
finish:
|
|
||||||
permissions:
|
|
||||||
checks: write # for coverallsapp/github-action to create new checks
|
|
||||||
needs: test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Coveralls Finished
|
|
||||||
uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
parallel-finished: true
|
|
||||||
13
.gitignore
vendored
@@ -1,4 +1,3 @@
|
|||||||
dist/
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
node_modules/
|
node_modules/
|
||||||
@@ -28,8 +27,8 @@ pidfile
|
|||||||
# templates
|
# templates
|
||||||
/public/templates
|
/public/templates
|
||||||
|
|
||||||
|
/public/sounds
|
||||||
/public/uploads
|
/public/uploads
|
||||||
/test/uploads
|
|
||||||
|
|
||||||
# compiled files
|
# compiled files
|
||||||
/public/stylesheet.css
|
/public/stylesheet.css
|
||||||
@@ -40,7 +39,6 @@ pidfile
|
|||||||
/public/acp.min.js.map
|
/public/acp.min.js.map
|
||||||
/public/installer.css
|
/public/installer.css
|
||||||
/public/installer.min.js
|
/public/installer.min.js
|
||||||
/public/bootstrap.min.css
|
|
||||||
/public/logo.png
|
/public/logo.png
|
||||||
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
|
||||||
@@ -60,16 +58,9 @@ tx.exe
|
|||||||
|
|
||||||
##Coverage output
|
##Coverage output
|
||||||
coverage
|
coverage
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
|
build
|
||||||
*.log
|
*.log
|
||||||
test/files/normalise.jpg.png
|
test/files/normalise.jpg.png
|
||||||
test/files/normalise-resized.jpg
|
test/files/normalise-resized.jpg
|
||||||
package-lock.json
|
package-lock.json
|
||||||
/package.json
|
|
||||||
*.mongodb
|
|
||||||
link-plugins.sh
|
|
||||||
test.sh
|
|
||||||
|
|
||||||
.docker/**
|
|
||||||
!**/.gitkeep
|
|
||||||
1
.husky/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
_
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npx --no-install commitlint --edit $1
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npx --no-install lint-staged
|
|
||||||
17
.jsbeautifyrc
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"indent_size": 4,
|
||||||
|
"indent_char": " ",
|
||||||
|
"indent_level": 0,
|
||||||
|
"indent_with_tabs": true,
|
||||||
|
"preserve_newlines": true,
|
||||||
|
"max_preserve_newlines": 10,
|
||||||
|
"jslint_happy": true,
|
||||||
|
"brace_style": "collapse",
|
||||||
|
"keep_array_indentation": false,
|
||||||
|
"keep_function_indentation": false,
|
||||||
|
"space_before_conditional": true,
|
||||||
|
"break_chained_methods": false,
|
||||||
|
"eval_code": false,
|
||||||
|
"unescape_strings": false,
|
||||||
|
"wrap_line_length": 0
|
||||||
|
}
|
||||||
89
.jshintrc
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
// JSHint Default Configuration File (as on JSHint website)
|
||||||
|
// See http://jshint.com/docs/ for more details
|
||||||
|
|
||||||
|
"maxerr" : 50, // {int} Maximum error before stopping
|
||||||
|
|
||||||
|
// Enforcing
|
||||||
|
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
|
||||||
|
"camelcase" : false, // true: Identifiers must be in camelCase
|
||||||
|
"curly" : true, // true: Require {} for every new block or scope
|
||||||
|
"eqeqeq" : true, // true: Require triple equals (===) for comparison
|
||||||
|
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
|
||||||
|
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
|
||||||
|
"indent" : 4, // {int} Number of spaces to use for indentation
|
||||||
|
"latedef" : false, // true: Require variables/functions to be defined before being used
|
||||||
|
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
|
||||||
|
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
|
||||||
|
"noempty" : true, // true: Prohibit use of empty blocks
|
||||||
|
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
|
||||||
|
"plusplus" : false, // true: Prohibit use of `++` & `--`
|
||||||
|
"quotmark" : false, // Quotation mark consistency:
|
||||||
|
// false : do nothing (default)
|
||||||
|
// true : ensure whatever is used is consistent
|
||||||
|
// "single" : require single quotes
|
||||||
|
// "double" : require double quotes
|
||||||
|
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
|
||||||
|
"unused" : true, // true: Require all defined variables be used
|
||||||
|
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
|
||||||
|
"trailing" : false, // true: Prohibit trailing whitespaces
|
||||||
|
"maxparams" : false, // {int} Max number of formal params allowed per function
|
||||||
|
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
|
||||||
|
"maxstatements" : false, // {int} Max number statements per function
|
||||||
|
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
|
||||||
|
"maxlen" : false, // {int} Max number of characters per line
|
||||||
|
|
||||||
|
// Relaxing
|
||||||
|
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
|
||||||
|
"boss" : false, // true: Tolerate assignments where comparisons would be expected
|
||||||
|
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
|
||||||
|
"eqnull" : false, // true: Tolerate use of `== null`
|
||||||
|
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
|
||||||
|
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
|
||||||
|
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
|
||||||
|
// (ex: `for each`, multiple try/catch, function expression…)
|
||||||
|
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
|
||||||
|
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
|
||||||
|
"funcscope" : false, // true: Tolerate defining variables inside control statements"
|
||||||
|
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
|
||||||
|
"iterator" : false, // true: Tolerate using the `__iterator__` property
|
||||||
|
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
|
||||||
|
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
|
||||||
|
"laxcomma" : false, // true: Tolerate comma-first style coding
|
||||||
|
"loopfunc" : false, // true: Tolerate functions being defined in loops
|
||||||
|
"multistr" : false, // true: Tolerate multi-line strings
|
||||||
|
"proto" : false, // true: Tolerate using the `__proto__` property
|
||||||
|
"scripturl" : false, // true: Tolerate script-targeted URLs
|
||||||
|
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
|
||||||
|
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
|
||||||
|
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
|
||||||
|
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
|
||||||
|
"validthis" : false, // true: Tolerate using this in a non-constructor function
|
||||||
|
|
||||||
|
// Environments
|
||||||
|
"browser" : true, // Web Browser (window, document, etc)
|
||||||
|
"couch" : false, // CouchDB
|
||||||
|
"devel" : true, // Development/debugging (alert, confirm, etc)
|
||||||
|
"dojo" : false, // Dojo Toolkit
|
||||||
|
"jquery" : true, // jQuery
|
||||||
|
"mootools" : false, // MooTools
|
||||||
|
"node" : true, // Node.js
|
||||||
|
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
|
||||||
|
"prototypejs" : false, // Prototype and Scriptaculous
|
||||||
|
"rhino" : false, // Rhino
|
||||||
|
"worker" : false, // Web Workers
|
||||||
|
"wsh" : false, // Windows Scripting Host
|
||||||
|
"yui" : false, // Yahoo User Interface
|
||||||
|
"mocha": true,
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
"nomen" : false, // true: Prohibit dangling `_` in variables
|
||||||
|
"onevar" : false, // true: Allow only one `var` statement per function
|
||||||
|
"passfail" : false, // true: Stop on first error
|
||||||
|
"white" : false, // true: Check against strict whitespace and indentation rules
|
||||||
|
|
||||||
|
// Custom Globals
|
||||||
|
"globals" : {
|
||||||
|
"Promise": true
|
||||||
|
} // additional predefined global variables
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
reporter: dot
|
|
||||||
timeout: 25000
|
|
||||||
exit: true
|
|
||||||
bail: true
|
|
||||||
32
.travis.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
services:
|
||||||
|
- redis-server
|
||||||
|
before_install:
|
||||||
|
- "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10"
|
||||||
|
- "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list"
|
||||||
|
- "sudo apt-get update"
|
||||||
|
- "sudo apt-get install mongodb-org-server"
|
||||||
|
- "sudo service mongod start"
|
||||||
|
before_script:
|
||||||
|
- sleep 15 # wait for mongodb to be ready
|
||||||
|
- sh -c "if [ '$DB' = 'mongodb' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"mongo\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"abcdef\\\",\\\"admin:password:confirm\\\":\\\"abcdef\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":27017,\\\"database\\\":0}\"; fi"
|
||||||
|
- sh -c "if [ '$DB' = 'redis' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"redis\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"abcdef\\\",\\\"admin:password:confirm\\\":\\\"abcdef\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":6379,\\\"database\\\":0}\"; fi"
|
||||||
|
after_success:
|
||||||
|
- "npm run coveralls"
|
||||||
|
language: node_js
|
||||||
|
env:
|
||||||
|
- CXX=g++-4.8 DB=mongodb
|
||||||
|
- CXX=g++-4.8 DB=redis
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
packages:
|
||||||
|
- g++-4.8
|
||||||
|
node_js:
|
||||||
|
- "8"
|
||||||
|
- "7"
|
||||||
|
- "6"
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
7067
.tx/config
12640
CHANGELOG.md
81
Dockerfile
@@ -1,76 +1,15 @@
|
|||||||
FROM node:lts as build
|
# The base image is the latest 4.x node (LTS) on jessie (debian)
|
||||||
|
# -onbuild will install the node dependencies found in the project package.json
|
||||||
|
# and copy its content in /usr/src/app, its WORKDIR
|
||||||
|
FROM node:4-onbuild
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
DAEMON=false \
|
daemon=false \
|
||||||
SILENT=false \
|
silent=false
|
||||||
USER=nodebb \
|
|
||||||
UID=1001 \
|
|
||||||
GID=1001
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app/
|
# nodebb setup will ask you for connection information to a redis (default), mongodb then run the forum
|
||||||
|
# nodebb upgrade is not included and might be desired
|
||||||
COPY . /usr/src/app/
|
CMD node app --setup && npm start
|
||||||
|
|
||||||
# Install corepack to allow usage of other package managers
|
|
||||||
RUN corepack enable
|
|
||||||
|
|
||||||
# Removing unnecessary files for us
|
|
||||||
RUN find . -mindepth 1 -maxdepth 1 -name '.*' ! -name '.' ! -name '..' -exec bash -c 'echo "Deleting {}"; rm -rf {}' \;
|
|
||||||
|
|
||||||
# Prepage package.json
|
|
||||||
RUN cp /usr/src/app/install/package.json /usr/src/app/
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& DEBIAN_FRONTEND=noninteractive \
|
|
||||||
apt-get -y --no-install-recommends install \
|
|
||||||
tini
|
|
||||||
|
|
||||||
RUN groupadd --gid ${GID} ${USER} \
|
|
||||||
&& useradd --uid ${UID} --gid ${GID} --home-dir /usr/src/app/ --shell /bin/bash ${USER} \
|
|
||||||
&& chown -R ${USER}:${USER} /usr/src/app/
|
|
||||||
|
|
||||||
USER ${USER}
|
|
||||||
|
|
||||||
RUN npm install --omit=dev \
|
|
||||||
&& rm -rf .npm
|
|
||||||
# TODO: generate lockfiles for each package manager
|
|
||||||
## pnpm import \
|
|
||||||
|
|
||||||
FROM node:lts-slim AS final
|
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
|
||||||
DAEMON=false \
|
|
||||||
SILENT=false \
|
|
||||||
USER=nodebb \
|
|
||||||
UID=1001 \
|
|
||||||
GID=1001
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app/
|
|
||||||
|
|
||||||
RUN corepack enable \
|
|
||||||
&& groupadd --gid ${GID} ${USER} \
|
|
||||||
&& useradd --uid ${UID} --gid ${GID} --home-dir /usr/src/app/ --shell /bin/bash ${USER} \
|
|
||||||
&& mkdir -p /usr/src/app/logs/ /opt/config/ \
|
|
||||||
&& chown -R ${USER}:${USER} /usr/src/app/ /opt/config/
|
|
||||||
|
|
||||||
COPY --from=build --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/install/docker/setup.json /usr/src/app/
|
|
||||||
COPY --from=build --chown=${USER}:${USER} /usr/bin/tini /usr/src/app/install/docker/entrypoint.sh /usr/local/bin/
|
|
||||||
|
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh \
|
|
||||||
&& chmod +x /usr/local/bin/tini
|
|
||||||
|
|
||||||
# TODO: Have docker-compose use environment variables to create files like setup.json and config.json.
|
|
||||||
# COPY --from=hairyhenderson/gomplate:stable /gomplate /usr/local/bin/gomplate
|
|
||||||
|
|
||||||
USER ${USER}
|
|
||||||
|
|
||||||
|
# the default port for NodeBB is exposed outside the container
|
||||||
EXPOSE 4567
|
EXPOSE 4567
|
||||||
|
|
||||||
VOLUME ["/usr/src/app/node_modules", "/usr/src/app/build", "/usr/src/app/public/uploads", "/opt/config/"]
|
|
||||||
|
|
||||||
# Utilising tini as our init system within the Docker container for graceful start-up and termination.
|
|
||||||
# Tini serves as an uncomplicated init system, adept at managing the reaping of zombie processes and forwarding signals.
|
|
||||||
# This approach is crucial to circumvent issues with unmanaged subprocesses and signal handling in containerised environments.
|
|
||||||
# By integrating tini, we enhance the reliability and stability of our Docker containers.
|
|
||||||
# Ensures smooth start-up and shutdown processes, and reliable, safe handling of signal processing.
|
|
||||||
ENTRYPOINT ["tini", "--", "entrypoint.sh"]
|
|
||||||
263
Gruntfile.js
@@ -1,201 +1,166 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
var fork = require('child_process').fork;
|
||||||
const nconf = require('nconf');
|
var env = process.env;
|
||||||
|
var worker;
|
||||||
nconf.argv().env({
|
var updateWorker;
|
||||||
separator: '__',
|
var initWorker;
|
||||||
});
|
var incomplete = [];
|
||||||
const winston = require('winston');
|
var running = 0;
|
||||||
const { fork } = require('child_process');
|
|
||||||
|
|
||||||
const { env } = process;
|
|
||||||
let worker;
|
|
||||||
|
|
||||||
env.NODE_ENV = env.NODE_ENV || 'development';
|
env.NODE_ENV = env.NODE_ENV || 'development';
|
||||||
|
|
||||||
const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json');
|
|
||||||
const prestart = require('./src/prestart');
|
|
||||||
|
|
||||||
prestart.loadConfig(configFile);
|
|
||||||
|
|
||||||
const db = require('./src/database');
|
|
||||||
const plugins = require('./src/plugins');
|
|
||||||
|
|
||||||
module.exports = function (grunt) {
|
module.exports = function (grunt) {
|
||||||
const args = [];
|
var args = [];
|
||||||
|
var initArgs = ['--build'];
|
||||||
if (!grunt.option('verbose')) {
|
if (!grunt.option('verbose')) {
|
||||||
args.push('--log-level=info');
|
args.push('--log-level=info');
|
||||||
nconf.set('log-level', 'info');
|
initArgs.push('--log-level=info');
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(action, filepath, target) {
|
||||||
|
var updateArgs = args.slice();
|
||||||
|
var compiling;
|
||||||
|
var time = Date.now();
|
||||||
|
|
||||||
|
if (target === 'lessUpdated_Client') {
|
||||||
|
compiling = 'clientCSS';
|
||||||
|
} else if (target === 'lessUpdated_Admin') {
|
||||||
|
compiling = 'acpCSS';
|
||||||
|
} else if (target === 'clientUpdated') {
|
||||||
|
compiling = 'js';
|
||||||
|
} else if (target === 'templatesUpdated') {
|
||||||
|
compiling = 'tpl';
|
||||||
|
} else if (target === 'langUpdated') {
|
||||||
|
compiling = 'lang';
|
||||||
|
} else if (target === 'serverUpdated') {
|
||||||
|
// Do nothing, just restart
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compiling && incomplete.indexOf(compiling) === -1) {
|
||||||
|
incomplete.push(compiling);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateArgs.push('--build');
|
||||||
|
updateArgs.push(incomplete.join(','));
|
||||||
|
|
||||||
|
worker.kill();
|
||||||
|
if (updateWorker) {
|
||||||
|
updateWorker.kill('SIGKILL');
|
||||||
|
}
|
||||||
|
updateWorker = fork('app.js', updateArgs, { env: env });
|
||||||
|
running += 1;
|
||||||
|
updateWorker.on('exit', function () {
|
||||||
|
running -= 1;
|
||||||
|
if (running === 0) {
|
||||||
|
worker = fork('app.js', args, {
|
||||||
|
env: env,
|
||||||
|
});
|
||||||
|
worker.on('message', function () {
|
||||||
|
if (incomplete.length) {
|
||||||
|
incomplete = [];
|
||||||
|
|
||||||
|
if (grunt.option('verbose')) {
|
||||||
|
grunt.log.writeln('NodeBB restarted in ' + (Date.now() - time) + ' ms');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
prestart.setupWinston();
|
|
||||||
|
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
watch: {},
|
watch: {
|
||||||
});
|
lessUpdated_Client: {
|
||||||
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
|
||||||
|
|
||||||
grunt.registerTask('default', ['watch']);
|
|
||||||
|
|
||||||
grunt.registerTask('init', async function () {
|
|
||||||
const done = this.async();
|
|
||||||
let pluginList = [];
|
|
||||||
if (!process.argv.includes('--core')) {
|
|
||||||
await db.init();
|
|
||||||
pluginList = await plugins.getActive();
|
|
||||||
addBaseThemes(pluginList);
|
|
||||||
if (!pluginList.includes('nodebb-plugin-composer-default')) {
|
|
||||||
pluginList.push('nodebb-plugin-composer-default');
|
|
||||||
}
|
|
||||||
if (!pluginList.includes('nodebb-theme-harmony')) {
|
|
||||||
pluginList.push('nodebb-theme-harmony');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const styleUpdated_Client = pluginList.map(p => `node_modules/${p}/*.scss`)
|
|
||||||
.concat(pluginList.map(p => `node_modules/${p}/*.css`))
|
|
||||||
.concat(pluginList.map(p => `node_modules/${p}/+(public|static|scss)/**/*.scss`))
|
|
||||||
.concat(pluginList.map(p => `node_modules/${p}/+(public|static)/**/*.css`));
|
|
||||||
|
|
||||||
const clientUpdated = pluginList.map(p => `node_modules/${p}/+(public|static)/**/*.js`);
|
|
||||||
const serverUpdated = pluginList.map(p => `node_modules/${p}/*.js`)
|
|
||||||
.concat(pluginList.map(p => `node_modules/${p}/+(lib|src)/**/*.js`));
|
|
||||||
|
|
||||||
const templatesUpdated = pluginList.map(p => `node_modules/${p}/+(public|static|templates)/**/*.tpl`);
|
|
||||||
const langUpdated = pluginList.map(p => `node_modules/${p}/+(public|static|languages)/**/*.json`);
|
|
||||||
const interval = 100;
|
|
||||||
grunt.config(['watch'], {
|
|
||||||
styleUpdated: {
|
|
||||||
files: [
|
files: [
|
||||||
'public/scss/**/*.scss',
|
'public/less/*.less',
|
||||||
...styleUpdated_Client,
|
'!public/less/admin/**/*.less',
|
||||||
|
'node_modules/nodebb-*/**/*.less',
|
||||||
|
'!node_modules/nodebb-*/node_modules/**',
|
||||||
|
'!node_modules/nodebb-*/.git/**',
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
interval: interval,
|
interval: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lessUpdated_Admin: {
|
||||||
|
files: [
|
||||||
|
'public/less/admin/**/*.less',
|
||||||
|
'node_modules/nodebb-*/**/*.less',
|
||||||
|
'!node_modules/nodebb-*/node_modules/**',
|
||||||
|
'!node_modules/nodebb-*/.git/**',
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
interval: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
clientUpdated: {
|
clientUpdated: {
|
||||||
files: [
|
files: [
|
||||||
'public/src/**/*.js',
|
'public/src/**/*.js',
|
||||||
'public/vendor/**/*.js',
|
'node_modules/nodebb-*/**/*.js',
|
||||||
...clientUpdated,
|
'!node_modules/nodebb-*/node_modules/**',
|
||||||
'node_modules/benchpressjs/build/benchpress.js',
|
'node_modules/benchpressjs/build/benchpress.js',
|
||||||
|
'!node_modules/nodebb-*/.git/**',
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
interval: interval,
|
interval: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
serverUpdated: {
|
serverUpdated: {
|
||||||
files: [
|
files: ['*.js', 'install/*.js', 'src/**/*.js'],
|
||||||
'app.js',
|
|
||||||
'install/*.js',
|
|
||||||
'src/**/*.js',
|
|
||||||
'public/src/modules/translator.common.js',
|
|
||||||
'public/src/modules/helpers.common.js',
|
|
||||||
'public/src/utils.common.js',
|
|
||||||
serverUpdated,
|
|
||||||
'!src/upgrades/**',
|
|
||||||
],
|
|
||||||
options: {
|
options: {
|
||||||
interval: interval,
|
interval: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
templatesUpdated: {
|
templatesUpdated: {
|
||||||
files: [
|
files: [
|
||||||
'src/views/**/*.tpl',
|
'src/views/**/*.tpl',
|
||||||
...templatesUpdated,
|
'node_modules/nodebb-*/**/*.tpl',
|
||||||
|
'!node_modules/nodebb-*/node_modules/**',
|
||||||
|
'!node_modules/nodebb-*/.git/**',
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
interval: interval,
|
interval: 1000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
langUpdated: {
|
langUpdated: {
|
||||||
files: [
|
files: [
|
||||||
'public/language/en-GB/*.json',
|
'public/language/en-GB/*.json',
|
||||||
'public/language/en-GB/**/*.json',
|
'public/language/en-GB/**/*.json',
|
||||||
...langUpdated,
|
'node_modules/nodebb-*/**/*.json',
|
||||||
|
'!node_modules/nodebb-*/node_modules/**',
|
||||||
|
'!node_modules/nodebb-*/.git/**',
|
||||||
|
'!node_modules/nodebb-*/plugin.json',
|
||||||
|
'!node_modules/nodebb-*/package.json',
|
||||||
|
'!node_modules/nodebb-*/theme.json',
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
interval: interval,
|
interval: 1000,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const build = require('./src/meta/build');
|
|
||||||
if (!grunt.option('skip')) {
|
|
||||||
await build.build(true, { watch: true });
|
|
||||||
}
|
|
||||||
run();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
function run() {
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
if (worker) {
|
|
||||||
worker.kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
const execArgv = [];
|
grunt.registerTask('default', ['watch']);
|
||||||
const inspect = process.argv.find(a => a.startsWith('--inspect'));
|
env.NODE_ENV = 'development';
|
||||||
|
|
||||||
if (inspect) {
|
|
||||||
execArgv.push(inspect);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (grunt.option('skip')) {
|
||||||
worker = fork('app.js', args, {
|
worker = fork('app.js', args, {
|
||||||
env,
|
env: env,
|
||||||
execArgv,
|
});
|
||||||
|
} else {
|
||||||
|
initWorker = fork('app.js', initArgs, {
|
||||||
|
env: env,
|
||||||
|
});
|
||||||
|
|
||||||
|
initWorker.on('exit', function () {
|
||||||
|
worker = fork('app.js', args, {
|
||||||
|
env: env,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
grunt.task.run('init');
|
grunt.event.on('watch', update);
|
||||||
|
|
||||||
grunt.event.removeAllListeners('watch');
|
|
||||||
grunt.event.on('watch', (action, filepath, target) => {
|
|
||||||
let compiling;
|
|
||||||
if (target === 'styleUpdated') {
|
|
||||||
compiling = ['clientCSS', 'acpCSS'];
|
|
||||||
} else if (target === 'clientUpdated') {
|
|
||||||
compiling = ['js'];
|
|
||||||
} else if (target === 'templatesUpdated') {
|
|
||||||
compiling = ['tpl'];
|
|
||||||
} else if (target === 'langUpdated') {
|
|
||||||
compiling = ['lang'];
|
|
||||||
} else if (target === 'serverUpdated') {
|
|
||||||
// empty require cache
|
|
||||||
const paths = ['./src/meta/build.js', './src/meta/index.js'];
|
|
||||||
paths.forEach(p => delete require.cache[require.resolve(p)]);
|
|
||||||
return run();
|
|
||||||
}
|
|
||||||
|
|
||||||
require('./src/meta/build').build(compiling, { webpack: false }, (err) => {
|
|
||||||
if (err) {
|
|
||||||
winston.error(err.stack);
|
|
||||||
}
|
|
||||||
if (worker) {
|
|
||||||
worker.send({ compiling: compiling });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function addBaseThemes(pluginList) {
|
|
||||||
let themeId = pluginList.find(p => p.includes('nodebb-theme-'));
|
|
||||||
if (!themeId) {
|
|
||||||
return pluginList;
|
|
||||||
}
|
|
||||||
let baseTheme;
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
baseTheme = require(`${themeId}/theme`).baseTheme;
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseTheme) {
|
|
||||||
pluginList.push(baseTheme);
|
|
||||||
themeId = baseTheme;
|
|
||||||
}
|
|
||||||
} while (baseTheme);
|
|
||||||
return pluginList;
|
|
||||||
}
|
|
||||||
|
|||||||
52
README.md
@@ -1,15 +1,23 @@
|
|||||||
# 
|
# <img alt="NodeBB" src="http://i.imgur.com/mYxPPtB.png" />
|
||||||
|
|
||||||
[](https://github.com/NodeBB/NodeBB/actions/workflows/test.yaml)
|
[](https://gitter.im/NodeBB/NodeBB?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
[](https://travis-ci.org/NodeBB/NodeBB)
|
||||||
[](https://coveralls.io/github/NodeBB/NodeBB?branch=master)
|
[](https://coveralls.io/github/NodeBB/NodeBB?branch=master)
|
||||||
|
[](https://david-dm.org/nodebb/nodebb)
|
||||||
[](https://codeclimate.com/github/NodeBB/NodeBB)
|
[](https://codeclimate.com/github/NodeBB/NodeBB)
|
||||||
[](https://discord.gg/p6YKPXu7er)
|
[](https://readthedocs.org/projects/nodebb/?badge=latest)
|
||||||
|
|
||||||
[**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB takes the best of the modern web: real-time streaming discussions, mobile responsiveness, and rich RESTful read/write APIs, while staying true to the original bulletin board/forum format → categorical hierarchies, local user accounts, and asynchronous messaging.
|
[**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and built on either a Redis or MongoDB database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB has many modern features out of the box such as social network integration and streaming discussions, while still making sure to be compatible with older browsers.
|
||||||
|
|
||||||
NodeBB by itself contains a "common core" of basic functionality, while additional functionality and integrations are enabled through the use of third-party plugins.
|
Additional functionality is enabled through the use of third-party plugins.
|
||||||
|
|
||||||
### [Try it now](//try.nodebb.org) | [Documentation](//docs.nodebb.org)
|
* [Demo & Meta Discussion](http://community.nodebb.org)
|
||||||
|
* [Documentation & Installation Instructions](http://docs.nodebb.org)
|
||||||
|
* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/)
|
||||||
|
* [NodeBB Blog](http://blog.nodebb.org)
|
||||||
|
* [Premium Hosting for NodeBB](http://www.nodebb.org/ "NodeBB")
|
||||||
|
* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
|
||||||
|
* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
@@ -24,30 +32,31 @@ NodeBB's theming engine is highly flexible and does not restrict your design cho
|
|||||||
[](http://i.imgur.com/LmHtPho.png)
|
[](http://i.imgur.com/LmHtPho.png)
|
||||||
[](http://i.imgur.com/paiJPJk.jpg)
|
[](http://i.imgur.com/paiJPJk.jpg)
|
||||||
|
|
||||||
Our minimalist "Harmony" theme gets you going right away, no coding experience required.
|
Our minimalist "Persona" theme gets you going right away, no coding experience required.
|
||||||
|
|
||||||
|
[](http://i.imgur.com/HwNEXGu.png)
|
||||||
|
[](http://i.imgur.com/II1byYs.png)
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## How can I follow along/contribute?
|
## How can I follow along/contribute?
|
||||||
|
|
||||||
* If you are a developer, feel free to check out the source and submit pull requests. We also have a wide array of [plugins](http://community.nodebb.org/category/7/nodebb-plugins) which would be a great starting point for learning the codebase.
|
* If you are a developer, feel free to check out the source and submit pull requests. We also have a wide array of [plugins](http://community.nodebb.org/category/7/nodebb-plugins) which would be a great starting point for learning the codebase.
|
||||||
* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extension of the base templates as well as styling via SCSS or CSS. NodeBB's base theme utilizes [Bootstrap 5](http://getbootstrap.com/) as a frontend toolkit.
|
* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extention of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
|
||||||
* If you know languages other than English you can help us translate NodeBB. We use [Transifex](https://explore.transifex.com/nodebb/nodebb/) for internationalization.
|
* If you know languages other than English you can help us translate NodeBB. We use [Transifex](https://www.transifex.com/projects/p/nodebb/) for internationalization.
|
||||||
* Please don't forget to **like**, **follow**, and **star our repo**! Join our growing [community](http://community.nodebb.org) to keep up to date with the latest NodeBB development.
|
* Please don't forget to **like**, **follow**, and **star our repo**! Join our growing [community](http://community.nodebb.org) to keep up to date with the latest NodeBB development.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
NodeBB requires the following software to be installed:
|
NodeBB requires the following software to be installed:
|
||||||
|
|
||||||
* A version of Node.js at least 18 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions))
|
* A version of Node.js at least 4 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions))
|
||||||
* MongoDB, version 3.6 or greater **or** Redis, version 2.8.9 or greater
|
* Redis, version 2.8.9 or greater **or** MongoDB, version 2.6 or greater
|
||||||
* If you are using [clustering](https://docs.nodebb.org/configuring/scaling/) you need Redis installed and configured.
|
|
||||||
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
|
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
[Please refer to platform-specific installation documentation](https://docs.nodebb.org/installing/os).
|
[Please refer to platform-specific installation documentation](https://docs.nodebb.org/installing/os)
|
||||||
If installing via the cloud (or using Docker), [please see cloud-based installation documentation](https://docs.nodebb.org/installing/cloud/).
|
|
||||||
|
|
||||||
## Securing NodeBB
|
## Securing NodeBB
|
||||||
|
|
||||||
@@ -60,7 +69,6 @@ It is important to ensure that your NodeBB and database servers are secured. Bea
|
|||||||
2. Use `iptables` to secure your server from unintended open ports. In Ubuntu, `ufw` provides a friendlier interface to working with `iptables`.
|
2. Use `iptables` to secure your server from unintended open ports. In Ubuntu, `ufw` provides a friendlier interface to working with `iptables`.
|
||||||
* e.g. If your NodeBB is proxied, no ports should be open except 80 (and possibly 22, for SSH access)
|
* e.g. If your NodeBB is proxied, no ports should be open except 80 (and possibly 22, for SSH access)
|
||||||
|
|
||||||
|
|
||||||
## Upgrading NodeBB
|
## Upgrading NodeBB
|
||||||
|
|
||||||
Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.nodebb.org/configuring/upgrade/)
|
Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.nodebb.org/configuring/upgrade/)
|
||||||
@@ -70,15 +78,3 @@ Detailed upgrade instructions are listed in [Upgrading NodeBB](https://docs.node
|
|||||||
NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
|
NodeBB is licensed under the **GNU General Public License v3 (GPL-3)** (http://www.gnu.org/copyleft/gpl.html).
|
||||||
|
|
||||||
Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive environment? Contact us at sales@nodebb.org.
|
Interested in a sublicense agreement for use of NodeBB in a non-free/restrictive environment? Contact us at sales@nodebb.org.
|
||||||
|
|
||||||
## More Information/Links
|
|
||||||
|
|
||||||
* [Demo](https://try.nodebb.org)
|
|
||||||
* [Developer Community](http://community.nodebb.org)
|
|
||||||
* [Documentation & Installation Instructions](https://docs.nodebb.org)
|
|
||||||
* [Help translate NodeBB](https://explore.transifex.com/nodebb/nodebb/)
|
|
||||||
* [NodeBB Blog](https://nodebb.org/blog)
|
|
||||||
* [Premium Hosting for NodeBB](https://www.nodebb.org/ "NodeBB")
|
|
||||||
* Unofficial IRC community – channel `#nodebb` on Libera.chat
|
|
||||||
* [Follow us on Twitter](http://www.twitter.com/NodeBB/ "NodeBB Twitter")
|
|
||||||
* [Like us on Facebook](http://www.facebook.com/NodeBB/ "NodeBB Facebook")
|
|
||||||
|
|||||||
290
app.js
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
NodeBB - A better forum platform for the modern web
|
NodeBB - A better forum platform for the modern web
|
||||||
https://github.com/NodeBB/NodeBB/
|
https://github.com/NodeBB/NodeBB/
|
||||||
Copyright (C) 2013-2021 NodeBB Inc.
|
Copyright (C) 2013-2017 NodeBB Inc.
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,64 +19,294 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('./require-main');
|
if (require.main !== module) {
|
||||||
|
require.main.require = function (path) {
|
||||||
const nconf = require('nconf');
|
return require(path);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var nconf = require('nconf');
|
||||||
nconf.argv().env({
|
nconf.argv().env({
|
||||||
separator: '__',
|
separator: '__',
|
||||||
|
lowerCase: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const winston = require('winston');
|
var url = require('url');
|
||||||
const path = require('path');
|
var async = require('async');
|
||||||
|
var winston = require('winston');
|
||||||
|
var path = require('path');
|
||||||
|
var pkg = require('./package.json');
|
||||||
|
var file = require('./src/file');
|
||||||
|
var debug = require('./src/meta/debugFork').debugging;
|
||||||
|
|
||||||
const file = require('./src/file');
|
|
||||||
|
|
||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
|
|
||||||
global.env = process.env.NODE_ENV || 'production';
|
global.env = process.env.NODE_ENV || 'production';
|
||||||
|
|
||||||
|
winston.remove(winston.transports.Console);
|
||||||
|
winston.add(winston.transports.Console, {
|
||||||
|
colorize: true,
|
||||||
|
timestamp: function () {
|
||||||
|
var date = new Date();
|
||||||
|
return nconf.get('json-logging') ? date.toJSON() : date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0, 8) + ' [' + global.process.pid + ']';
|
||||||
|
},
|
||||||
|
level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose'),
|
||||||
|
json: (!!nconf.get('json-logging')),
|
||||||
|
stringify: (!!nconf.get('json-logging')),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
var winstonCommon = require('winston/lib/winston/common');
|
||||||
|
// Override to use real console.log etc for VSCode debugger
|
||||||
|
winston.transports.Console.prototype.log = function (level, message, meta, callback) {
|
||||||
|
const output = winstonCommon.log(Object.assign({}, this, {
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
meta,
|
||||||
|
}));
|
||||||
|
|
||||||
|
console[level in console ? level : 'log'](output);
|
||||||
|
|
||||||
|
setImmediate(callback, null, true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Alternate configuration file support
|
// Alternate configuration file support
|
||||||
const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json');
|
var configFile = path.join(__dirname, '/config.json');
|
||||||
|
|
||||||
const configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
|
if (nconf.get('config')) {
|
||||||
|
configFile = path.resolve(__dirname, nconf.get('config'));
|
||||||
|
}
|
||||||
|
|
||||||
const prestart = require('./src/prestart');
|
var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
|
||||||
|
|
||||||
prestart.loadConfig(configFile);
|
loadConfig();
|
||||||
prestart.setupWinston();
|
versionCheck();
|
||||||
prestart.versionCheck();
|
|
||||||
winston.verbose('* using configuration stored in: %s', configFile);
|
|
||||||
|
|
||||||
if (!process.send) {
|
if (!process.send) {
|
||||||
// If run using `node app`, log GNU copyright info along with server info
|
// If run using `node app`, log GNU copyright info along with server info
|
||||||
winston.info(`NodeBB v${nconf.get('version')} Copyright (C) 2013-${(new Date()).getFullYear()} NodeBB Inc.`);
|
winston.info('NodeBB v' + nconf.get('version') + ' Copyright (C) 2013-' + (new Date()).getFullYear() + ' NodeBB Inc.');
|
||||||
winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
|
winston.info('This program comes with ABSOLUTELY NO WARRANTY.');
|
||||||
winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
|
winston.info('This is free software, and you are welcome to redistribute it under certain conditions.');
|
||||||
winston.info('');
|
winston.info('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (nconf.get('setup') || nconf.get('install')) {
|
if (nconf.get('setup') || nconf.get('install')) {
|
||||||
require('./src/cli/setup').setup();
|
setup();
|
||||||
} else if (!configExists) {
|
} else if (!configExists) {
|
||||||
require('./install/web').install(nconf.get('port'));
|
require('./install/web').install(nconf.get('port'));
|
||||||
} else if (nconf.get('upgrade')) {
|
} else if (nconf.get('upgrade')) {
|
||||||
require('./src/cli/upgrade').upgrade(true);
|
upgrade();
|
||||||
} else if (nconf.get('reset')) {
|
} else if (nconf.get('reset')) {
|
||||||
require('./src/cli/reset').reset({
|
async.waterfall([
|
||||||
theme: nconf.get('t'),
|
async.apply(require('./src/reset').reset),
|
||||||
plugin: nconf.get('p'),
|
async.apply(require('./src/meta/build').buildAll),
|
||||||
widgets: nconf.get('w'),
|
], function (err) {
|
||||||
settings: nconf.get('s'),
|
process.exit(err ? 1 : 0);
|
||||||
all: nconf.get('a'),
|
|
||||||
});
|
});
|
||||||
} else if (nconf.get('activate')) {
|
} else if (nconf.get('activate')) {
|
||||||
require('./src/cli/manage').activate(nconf.get('activate'));
|
activate();
|
||||||
} else if (nconf.get('plugins') && typeof nconf.get('plugins') !== 'object') {
|
} else if (nconf.get('plugins')) {
|
||||||
require('./src/cli/manage').listPlugins();
|
listPlugins();
|
||||||
} else if (nconf.get('build')) {
|
} else if (nconf.get('build')) {
|
||||||
require('./src/cli/manage').build(nconf.get('build'));
|
require('./src/meta/build').build(nconf.get('build'));
|
||||||
} else if (nconf.get('events')) {
|
} else if (nconf.get('events')) {
|
||||||
require('./src/cli/manage').listEvents();
|
async.series([
|
||||||
|
async.apply(require('./src/database').init),
|
||||||
|
async.apply(require('./src/events').output),
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
require('./src/start').start();
|
require('./src/start').start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadConfig(callback) {
|
||||||
|
winston.verbose('* using configuration stored in: %s', configFile);
|
||||||
|
|
||||||
|
nconf.file({
|
||||||
|
file: configFile,
|
||||||
|
});
|
||||||
|
|
||||||
|
nconf.defaults({
|
||||||
|
base_dir: __dirname,
|
||||||
|
themes_path: path.join(__dirname, 'node_modules'),
|
||||||
|
upload_path: 'public/uploads',
|
||||||
|
views_dir: path.join(__dirname, 'build/public/templates'),
|
||||||
|
version: pkg.version,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!nconf.get('isCluster')) {
|
||||||
|
nconf.set('isPrimary', 'true');
|
||||||
|
nconf.set('isCluster', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure themes_path is a full filepath
|
||||||
|
nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
|
||||||
|
nconf.set('core_templates_path', path.join(__dirname, 'src/views'));
|
||||||
|
nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates'));
|
||||||
|
|
||||||
|
nconf.set('upload_path', path.resolve(nconf.get('base_dir'), nconf.get('upload_path')));
|
||||||
|
|
||||||
|
if (nconf.get('url')) {
|
||||||
|
nconf.set('url_parsed', url.parse(nconf.get('url')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly cast 'jobsDisabled' as Bool
|
||||||
|
var castAsBool = ['jobsDisabled'];
|
||||||
|
nconf.stores.env.readOnly = false;
|
||||||
|
castAsBool.forEach(function (prop) {
|
||||||
|
var value = nconf.get(prop);
|
||||||
|
if (value) {
|
||||||
|
nconf.set(prop, typeof value === 'boolean' ? value : String(value).toLowerCase() === 'true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
nconf.stores.env.readOnly = true;
|
||||||
|
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
winston.info('NodeBB Setup Triggered via Command Line');
|
||||||
|
|
||||||
|
var install = require('./src/install');
|
||||||
|
var build = require('./src/meta/build');
|
||||||
|
|
||||||
|
process.stdout.write('\nWelcome to NodeBB!\n');
|
||||||
|
process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
|
||||||
|
process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
async.apply(install.setup),
|
||||||
|
async.apply(loadConfig),
|
||||||
|
async.apply(build.buildAll),
|
||||||
|
], function (err, data) {
|
||||||
|
// Disregard build step data
|
||||||
|
data = data[0];
|
||||||
|
|
||||||
|
var separator = ' ';
|
||||||
|
if (process.stdout.columns > 10) {
|
||||||
|
for (var x = 0, cols = process.stdout.columns - 10; x < cols; x += 1) {
|
||||||
|
separator += '=';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
process.stdout.write('\n' + separator + '\n\n');
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
winston.error('There was a problem completing NodeBB setup: ', err.message);
|
||||||
|
} else {
|
||||||
|
if (data.hasOwnProperty('password')) {
|
||||||
|
process.stdout.write('An administrative user was automatically created for you:\n');
|
||||||
|
process.stdout.write(' Username: ' + data.username + '\n');
|
||||||
|
process.stdout.write(' Password: ' + data.password + '\n');
|
||||||
|
process.stdout.write('\n');
|
||||||
|
}
|
||||||
|
process.stdout.write('NodeBB Setup Completed. Run \'./nodebb start\' to manually start your NodeBB server.\n');
|
||||||
|
|
||||||
|
// If I am a child process, notify the parent of the returned data before exiting (useful for notifying
|
||||||
|
// hosts of auto-generated username/password during headless setups)
|
||||||
|
if (process.send) {
|
||||||
|
process.send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function upgrade() {
|
||||||
|
var db = require('./src/database');
|
||||||
|
var meta = require('./src/meta');
|
||||||
|
var upgrade = require('./src/upgrade');
|
||||||
|
var build = require('./src/meta/build');
|
||||||
|
var tasks = [db.init, meta.configs.init, upgrade.run, build.buildAll];
|
||||||
|
|
||||||
|
if (nconf.get('upgrade') !== true) {
|
||||||
|
// Likely an upgrade script name passed in
|
||||||
|
tasks[2] = async.apply(upgrade.runSingle, nconf.get('upgrade'));
|
||||||
|
|
||||||
|
// Skip build
|
||||||
|
tasks.pop();
|
||||||
|
}
|
||||||
|
// disable mongo timeouts during upgrade
|
||||||
|
nconf.set('mongo:options:socketTimeoutMS', 0);
|
||||||
|
async.series(tasks, function (err) {
|
||||||
|
if (err) {
|
||||||
|
winston.error(err.stack);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function activate() {
|
||||||
|
var db = require('./src/database');
|
||||||
|
var plugins = require('./src/plugins');
|
||||||
|
var events = require('./src/events');
|
||||||
|
var plugin = nconf.get('activate');
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
|
db.init(next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
if (plugin.indexOf('nodebb-') !== 0) {
|
||||||
|
// Allow omission of `nodebb-plugin-`
|
||||||
|
plugin = 'nodebb-plugin-' + plugin;
|
||||||
|
}
|
||||||
|
plugins.isInstalled(plugin, next);
|
||||||
|
},
|
||||||
|
function (isInstalled, next) {
|
||||||
|
if (!isInstalled) {
|
||||||
|
return next(new Error('plugin not installed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
winston.info('Activating plugin `%s`', plugin);
|
||||||
|
db.sortedSetAdd('plugins:active', 0, plugin, next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
events.log({
|
||||||
|
type: 'plugin-activate',
|
||||||
|
text: plugin,
|
||||||
|
}, next);
|
||||||
|
},
|
||||||
|
], function (err) {
|
||||||
|
if (err) {
|
||||||
|
winston.error(err.message);
|
||||||
|
}
|
||||||
|
process.exit(err ? 1 : 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function listPlugins() {
|
||||||
|
require('./src/database').init(function (err) {
|
||||||
|
if (err) {
|
||||||
|
winston.error(err.stack);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var db = require('./src/database');
|
||||||
|
|
||||||
|
db.getSortedSetRange('plugins:active', 0, -1, function (err, plugins) {
|
||||||
|
if (err) {
|
||||||
|
winston.error(err.stack);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
winston.info('Active plugins: \n\t - ' + plugins.join('\n\t - '));
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function versionCheck() {
|
||||||
|
var version = process.version.slice(1);
|
||||||
|
var range = pkg.engines.node;
|
||||||
|
var semver = require('semver');
|
||||||
|
var compatible = semver.satisfies(version, range);
|
||||||
|
|
||||||
|
if (!compatible) {
|
||||||
|
winston.warn('Your version of Node.js is too outdated for NodeBB. Please update your version of Node.js.');
|
||||||
|
winston.warn('Recommended ' + range.green + ', '.reset + version.yellow + ' provided\n'.reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
4
build/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
*
|
|
||||||
*/
|
|
||||||
!export
|
|
||||||
!.gitignore
|
|
||||||
3
build/export/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
.
|
|
||||||
!.gitignore
|
|
||||||
!README
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
This directory contains archives of user uploads that are prepared on-demand
|
|
||||||
when a user wants to retrieve a copy of their uploaded content.
|
|
||||||
|
|
||||||
You can delete the files in here at will. They will just be regenerated if
|
|
||||||
requested again.
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
extends: ['@commitlint/config-angular'],
|
|
||||||
rules: {
|
|
||||||
'header-max-length': [1, 'always', 72],
|
|
||||||
'type-enum': [
|
|
||||||
2,
|
|
||||||
'always',
|
|
||||||
[
|
|
||||||
'breaking',
|
|
||||||
'build',
|
|
||||||
'chore',
|
|
||||||
'ci',
|
|
||||||
'docs',
|
|
||||||
'feat',
|
|
||||||
'fix',
|
|
||||||
'perf',
|
|
||||||
'refactor',
|
|
||||||
'revert',
|
|
||||||
'style',
|
|
||||||
'test',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
FROM node:lts AS git
|
|
||||||
|
|
||||||
ENV USER=nodebb \
|
|
||||||
UID=1001 \
|
|
||||||
GID=1001
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app/
|
|
||||||
|
|
||||||
RUN groupadd --gid ${GID} ${USER} \
|
|
||||||
&& useradd --uid ${UID} --gid ${GID} --home-dir /usr/src/app/ --shell /bin/bash ${USER} \
|
|
||||||
&& chown -R ${USER}:${USER} /usr/src/app/
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get -y --no-install-recommends install tini
|
|
||||||
|
|
||||||
USER ${USER}
|
|
||||||
|
|
||||||
# Change to the git branch you want to test
|
|
||||||
RUN git clone --recurse-submodules -j8 --depth 1 https://github.com/NodeBB/NodeBB.git .
|
|
||||||
|
|
||||||
RUN find . -mindepth 1 -maxdepth 1 -name '.*' ! -name '.' ! -name '..' -exec bash -c 'echo "Deleting {}"; rm -rf {}' \;
|
|
||||||
|
|
||||||
FROM node:lts AS node_modules_touch
|
|
||||||
|
|
||||||
ENV NODE_ENV=development \
|
|
||||||
USER=nodebb \
|
|
||||||
UID=1001 \
|
|
||||||
GID=1001
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app/
|
|
||||||
|
|
||||||
RUN corepack enable \
|
|
||||||
&& groupadd --gid ${GID} ${USER} \
|
|
||||||
&& useradd --uid ${UID} --gid ${GID} --home-dir /usr/src/app/ --shell /bin/bash ${USER} \
|
|
||||||
&& chown -R ${USER}:${USER} /usr/src/app/
|
|
||||||
|
|
||||||
COPY --from=git --chown=${USER}:${USER} /usr/src/app/install/package.json /usr/src/app/
|
|
||||||
|
|
||||||
USER ${USER}
|
|
||||||
|
|
||||||
RUN npm install \
|
|
||||||
&& rm -rf .npm
|
|
||||||
|
|
||||||
FROM node:lts-slim AS final
|
|
||||||
|
|
||||||
ENV NODE_ENV=development \
|
|
||||||
DAEMON=false \
|
|
||||||
SILENT=false \
|
|
||||||
USER=nodebb \
|
|
||||||
UID=1001 \
|
|
||||||
GID=1001
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app/
|
|
||||||
|
|
||||||
RUN corepack enable \
|
|
||||||
&& groupadd --gid ${GID} ${USER} \
|
|
||||||
&& useradd --uid ${UID} --gid ${GID} --home-dir /usr/src/app/ --shell /bin/bash ${USER} \
|
|
||||||
&& mkdir -p /usr/src/app/logs/ /opt/config/ \
|
|
||||||
&& chown -R ${USER}:${USER} /usr/src/app/ /opt/config/
|
|
||||||
|
|
||||||
COPY --from=build --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/install/docker/setup.json /usr/src/app/
|
|
||||||
COPY --from=build --chown=${USER}:${USER} /usr/bin/tini /usr/src/app/install/docker/entrypoint.sh /usr/local/bin/
|
|
||||||
COPY --from=node_modules_touch --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/
|
|
||||||
COPY --from=git --chown=${USER}:${USER} /usr/src/app/ /usr/src/app/
|
|
||||||
|
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh \
|
|
||||||
&& chmod +x /usr/local/bin/tini
|
|
||||||
|
|
||||||
# TODO: Have docker-compose use environment variables to create files like setup.json and config.json.
|
|
||||||
# COPY --from=hairyhenderson/gomplate:stable /gomplate /usr/local/bin/gomplate
|
|
||||||
|
|
||||||
USER ${USER}
|
|
||||||
|
|
||||||
EXPOSE 4567
|
|
||||||
|
|
||||||
VOLUME ["/usr/src/app/node_modules", "/usr/src/app/build", "/usr/src/app/public/uploads", "/opt/config/"]
|
|
||||||
|
|
||||||
ENTRYPOINT ["tini", "--", "entrypoint.sh"]
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
nodebb:
|
|
||||||
build: .
|
|
||||||
# image: ghcr.io/nodebb/nodebb:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- '4567:4567' # comment this out if you don't want to expose NodeBB to the host, or change the first number to any port you want
|
|
||||||
volumes:
|
|
||||||
- nodebb-build:/usr/src/app/build
|
|
||||||
- nodebb-uploads:/usr/src/app/public/uploads
|
|
||||||
- nodebb-config:/opt/config
|
|
||||||
- ./install/docker/setup.json:/usr/src/app/setup.json
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:17.4-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: nodebb
|
|
||||||
POSTGRES_PASSWORD: nodebb
|
|
||||||
POSTGRES_DB: nodebb
|
|
||||||
volumes:
|
|
||||||
- postgres-data:/var/lib/postgresql/data
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:7.4.2-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
|
|
||||||
# command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
profiles:
|
|
||||||
- redis
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres-data:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/database/postgresql/data
|
|
||||||
|
|
||||||
redis-data:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/database/redis
|
|
||||||
|
|
||||||
nodebb-build:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/build
|
|
||||||
|
|
||||||
nodebb-uploads:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/public/uploads
|
|
||||||
|
|
||||||
nodebb-config:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/config
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
nodebb:
|
|
||||||
build: .
|
|
||||||
# image: ghcr.io/nodebb/nodebb:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- '4567:4567' # comment this out if you don't want to expose NodeBB to the host, or change the first number to any port you want
|
|
||||||
volumes:
|
|
||||||
- nodebb-build:/usr/src/app/build
|
|
||||||
- nodebb-uploads:/usr/src/app/public/uploads
|
|
||||||
- nodebb-config:/opt/config
|
|
||||||
- ./install/docker/setup.json:/usr/src/app/setup.json
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:7.4.2-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
|
|
||||||
# command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
redis-data:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/database/redis
|
|
||||||
|
|
||||||
nodebb-build:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/build
|
|
||||||
|
|
||||||
nodebb-uploads:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/public/uploads
|
|
||||||
|
|
||||||
nodebb-config:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/config
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
services:
|
|
||||||
nodebb:
|
|
||||||
build: .
|
|
||||||
# image: ghcr.io/nodebb/nodebb:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- '4567:4567' # comment this out if you don't want to expose NodeBB to the host, or change the first number to any port you want
|
|
||||||
volumes:
|
|
||||||
- nodebb-build:/usr/src/app/build
|
|
||||||
- nodebb-uploads:/usr/src/app/public/uploads
|
|
||||||
- nodebb-config:/opt/config
|
|
||||||
- ./install/docker/setup.json:/usr/src/app/setup.json
|
|
||||||
|
|
||||||
mongo:
|
|
||||||
image: 'mongo:7-jammy'
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- '27017:27017'
|
|
||||||
environment:
|
|
||||||
MONGO_INITDB_ROOT_USERNAME: nodebb
|
|
||||||
MONGO_INITDB_ROOT_PASSWORD: nodebb
|
|
||||||
MONGO_INITDB_DATABASE: nodebb
|
|
||||||
volumes:
|
|
||||||
- mongo-data:/data/db
|
|
||||||
- ./install/docker/mongodb-user-init.js:/docker-entrypoint-initdb.d/user-init.js
|
|
||||||
redis:
|
|
||||||
image: redis:7.4.2-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
|
|
||||||
# command: ['redis-server', '--save', '60', '1', '--loglevel', 'warning'] # uncomment if you want to use snapshotting instead of AOF
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
profiles:
|
|
||||||
- redis
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:17.4-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: nodebb
|
|
||||||
POSTGRES_PASSWORD: nodebb
|
|
||||||
POSTGRES_DB: nodebb
|
|
||||||
volumes:
|
|
||||||
- postgres-data:/var/lib/postgresql/data
|
|
||||||
profiles:
|
|
||||||
- postgres
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
mongo-data:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/database/mongo/data
|
|
||||||
|
|
||||||
redis-data:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/database/redis
|
|
||||||
|
|
||||||
postgres-data:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/database/postgresql/data
|
|
||||||
|
|
||||||
nodebb-build:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/build
|
|
||||||
|
|
||||||
nodebb-uploads:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/public/uploads
|
|
||||||
|
|
||||||
nodebb-config:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
o: bind
|
|
||||||
type: none
|
|
||||||
device: ./.docker/config
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "Announcements regarding our community",
|
"description": "Announcements regarding our community",
|
||||||
"descriptionParsed": "<p>Announcements regarding our community</p>\n",
|
"descriptionParsed": "<p>Announcements regarding our community</p>\n",
|
||||||
"bgColor": "#fda34b",
|
"bgColor": "#fda34b",
|
||||||
"color": "#ffffff",
|
"color": "#fff",
|
||||||
"icon" : "fa-bullhorn",
|
"icon" : "fa-bullhorn",
|
||||||
"order": 1
|
"order": 1
|
||||||
},
|
},
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"description": "A place to talk about whatever you want",
|
"description": "A place to talk about whatever you want",
|
||||||
"descriptionParsed": "<p>A place to talk about whatever you want</p>\n",
|
"descriptionParsed": "<p>A place to talk about whatever you want</p>\n",
|
||||||
"bgColor": "#59b3d0",
|
"bgColor": "#59b3d0",
|
||||||
"color": "#ffffff",
|
"color": "#fff",
|
||||||
"icon" : "fa-comments-o",
|
"icon" : "fa-comments-o",
|
||||||
"order": 2
|
"order": 2
|
||||||
},
|
},
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"description": "Blog posts from individual members",
|
"description": "Blog posts from individual members",
|
||||||
"descriptionParsed": "<p>Blog posts from individual members</p>\n",
|
"descriptionParsed": "<p>Blog posts from individual members</p>\n",
|
||||||
"bgColor": "#86ba4b",
|
"bgColor": "#86ba4b",
|
||||||
"color": "#ffffff",
|
"color": "#fff",
|
||||||
"icon" : "fa-newspaper-o",
|
"icon" : "fa-newspaper-o",
|
||||||
"order": 4
|
"order": 4
|
||||||
},
|
},
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"description": "Got a question? Ask away!",
|
"description": "Got a question? Ask away!",
|
||||||
"descriptionParsed": "<p>Got a question? Ask away!</p>\n",
|
"descriptionParsed": "<p>Got a question? Ask away!</p>\n",
|
||||||
"bgColor": "#e95c5a",
|
"bgColor": "#e95c5a",
|
||||||
"color": "#ffffff",
|
"color": "#fff",
|
||||||
"icon" : "fa-question",
|
"icon" : "fa-question",
|
||||||
"order": 3
|
"order": 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,205 +1,41 @@
|
|||||||
{
|
{
|
||||||
"title": "NodeBB",
|
"title": "NodeBB",
|
||||||
"showSiteTitle": 1,
|
"showSiteTitle": 1,
|
||||||
"defaultLang": "en-GB",
|
|
||||||
"loginDays": 14,
|
|
||||||
"loginSeconds": 0,
|
|
||||||
"sessionDuration": 0,
|
|
||||||
"loginAttempts": 5,
|
|
||||||
"lockoutDuration": 60,
|
|
||||||
"adminReloginDuration": 60,
|
|
||||||
"postDelay": 10,
|
"postDelay": 10,
|
||||||
"initialPostDelay": 10,
|
"initialPostDelay": 10,
|
||||||
"newbiePostDelay": 120,
|
"newbiePostDelay": 120,
|
||||||
"postEditDuration": 0,
|
"newbiePostDelayThreshold": 3,
|
||||||
"newbiePostEditDuration": 3600,
|
|
||||||
"postDeleteDuration": 0,
|
|
||||||
"enablePostHistory": 1,
|
|
||||||
"topicBacklinks": 1,
|
|
||||||
"postCacheSize": 20971520,
|
|
||||||
"disableChat": 0,
|
|
||||||
"chatEditDuration": 0,
|
|
||||||
"chatDeleteDuration": 0,
|
|
||||||
"chatMessageDelay": 2000,
|
|
||||||
"newbieChatMessageDelay": 120000,
|
|
||||||
"notificationSendDelay": 60,
|
|
||||||
"newbieReputationThreshold": 3,
|
|
||||||
"postQueue": 0,
|
|
||||||
"postQueueReputationThreshold": 0,
|
|
||||||
"groupsExemptFromPostQueue": ["administrators", "Global Moderators"],
|
|
||||||
"groupsExemptFromNewUserRestrictions": ["administrators", "Global Moderators"],
|
|
||||||
"groupsExemptFromMaintenanceMode": ["administrators", "Global Moderators"],
|
|
||||||
"minimumPostLength": 8,
|
"minimumPostLength": 8,
|
||||||
"maximumPostLength": 32767,
|
"maximumPostLength": 32767,
|
||||||
"systemTags": "",
|
|
||||||
"minimumTagsPerTopic": 0,
|
"minimumTagsPerTopic": 0,
|
||||||
"maximumTagsPerTopic": 5,
|
"maximumTagsPerTopic": 5,
|
||||||
"minimumTagLength": 3,
|
"minimumTagLength": 3,
|
||||||
"maximumTagLength": 15,
|
"maximumTagLength": 15,
|
||||||
"undoTimeout": 10000,
|
"allowGuestSearching": 0,
|
||||||
"allowTopicsThumbnail": 1,
|
"allowTopicsThumbnail": 0,
|
||||||
"registrationType": "normal",
|
"registrationType": "normal",
|
||||||
"registrationApprovalType": "normal",
|
"allowLocalLogin": 1,
|
||||||
"allowAccountDelete": 1,
|
"allowAccountDelete": 1,
|
||||||
"privateUploads": 0,
|
"allowFileUploads": 0,
|
||||||
"allowedFileExtensions": "png,jpg,bmp,txt,webp,webm,mp4,gif",
|
"allowedFileExtensions": "png,jpg,bmp",
|
||||||
"uploadRateLimitThreshold": 10,
|
|
||||||
"uploadRateLimitCooldown": 60,
|
|
||||||
"allowUserHomePage": 1,
|
"allowUserHomePage": 1,
|
||||||
"allowMultipleBadges": 1,
|
|
||||||
"maximumFileSize": 2048,
|
"maximumFileSize": 2048,
|
||||||
"stripEXIFData": 1,
|
|
||||||
"orphanExpiryDays": 0,
|
|
||||||
"resizeImageWidthThreshold": 2000,
|
|
||||||
"resizeImageWidth": 760,
|
|
||||||
"resizeImageKeepOriginal": 1,
|
|
||||||
"rejectImageWidth": 5000,
|
|
||||||
"rejectImageHeight": 5000,
|
|
||||||
"resizeImageQuality": 80,
|
|
||||||
"topicThumbSize": 512,
|
|
||||||
"minimumTitleLength": 3,
|
"minimumTitleLength": 3,
|
||||||
"maximumTitleLength": 255,
|
"maximumTitleLength": 255,
|
||||||
"minimumUsernameLength": 2,
|
"minimumUsernameLength": 2,
|
||||||
"maximumUsernameLength": 16,
|
"maximumUsernameLength": 16,
|
||||||
"minimumPasswordLength": 6,
|
"minimumPasswordLength": 6,
|
||||||
"minimumPasswordStrength": 1,
|
|
||||||
"maximumSignatureLength": 255,
|
"maximumSignatureLength": 255,
|
||||||
"maximumAboutMeLength": 1000,
|
"maximumAboutMeLength": 1000,
|
||||||
"maximumUsersInChatRoom": 0,
|
|
||||||
"maximumChatMessageLength": 1000,
|
|
||||||
"maximumChatRoomNameLength": 50,
|
|
||||||
"maximumProfileImageSize": 256,
|
"maximumProfileImageSize": 256,
|
||||||
"maximumCoverImageSize": 2048,
|
"maximumCoverImageSize": 2048,
|
||||||
"profileImageDimension": 200,
|
"profileImageDimension": 200,
|
||||||
"profile:convertProfileImageToPNG": 0,
|
"requireEmailConfirmation": 0,
|
||||||
"profile:keepAllUserImages": 0,
|
|
||||||
"gdpr_enabled": 1,
|
|
||||||
"allowProfileImageUploads": 1,
|
"allowProfileImageUploads": 1,
|
||||||
"teaserPost": "last-reply",
|
"teaserPost": "last-reply",
|
||||||
"showPostPreviewsOnHover": 1,
|
|
||||||
"allowPrivateGroups": 1,
|
"allowPrivateGroups": 1,
|
||||||
"unreadCutoff": 2,
|
"unreadCutoff": 2,
|
||||||
"bookmarkThreshold": 5,
|
"bookmarkThreshold": 5,
|
||||||
"autoDetectLang": 1,
|
"topicsPerList": 20,
|
||||||
"reputation:disabled": 0,
|
"autoDetectLang": 1
|
||||||
"downvote:disabled": 0,
|
|
||||||
"disableSignatures": 0,
|
|
||||||
"signatures:hideDuplicates": 0,
|
|
||||||
"upvotesPerDay": 20,
|
|
||||||
"upvotesPerUserPerDay": 6,
|
|
||||||
"downvotesPerDay": 10,
|
|
||||||
"downvotesPerUserPerDay": 3,
|
|
||||||
"min:rep:chat": 0,
|
|
||||||
"min:rep:downvote": 0,
|
|
||||||
"min:rep:upvote": 0,
|
|
||||||
"min:rep:post-links": 0,
|
|
||||||
"min:rep:flag": 0,
|
|
||||||
"min:rep:profile-picture": 0,
|
|
||||||
"min:rep:cover-picture": 0,
|
|
||||||
"min:rep:website": 0,
|
|
||||||
"min:rep:aboutme": 0,
|
|
||||||
"min:rep:signature": 0,
|
|
||||||
"flags:limitPerTarget": 0,
|
|
||||||
"flags:postFlagsPerDay": 10,
|
|
||||||
"flags:userFlagsPerDay": 10,
|
|
||||||
"flags:autoFlagOnDownvoteThreshold": 0,
|
|
||||||
"flags:actionOnResolve": "rescind",
|
|
||||||
"flags:actionOnReject": "rescind",
|
|
||||||
"notificationType_upvote": "notification",
|
|
||||||
"notificationType_new-topic": "notification",
|
|
||||||
"notificationType_new-topic-with-tag": "notification",
|
|
||||||
"notificationType_new-topic-in-category": "notification",
|
|
||||||
"notificationType_new-reply": "notification",
|
|
||||||
"notificationType_post-edit": "notification",
|
|
||||||
"notificationType_follow": "notification",
|
|
||||||
"notificationType_new-chat": "notification",
|
|
||||||
"notificationType_new-group-chat": "notification",
|
|
||||||
"notificationType_new-public-chat": "none",
|
|
||||||
"notificationType_group-invite": "notification",
|
|
||||||
"notificationType_group-leave": "notification",
|
|
||||||
"notificationType_group-request-membership": "notification",
|
|
||||||
"notificationType_mention": "notification",
|
|
||||||
"notificationType_new-register": "notification",
|
|
||||||
"notificationType_post-queue": "notification",
|
|
||||||
"notificationType_new-post-flag": "notification",
|
|
||||||
"notificationType_new-user-flag": "notification",
|
|
||||||
"topicStaleDays": 60,
|
|
||||||
"maxTopicsPerPage": 20,
|
|
||||||
"maxPostsPerPage": 20,
|
|
||||||
"topicsPerPage": 20,
|
|
||||||
"postsPerPage": 20,
|
|
||||||
"categoriesPerPage": 50,
|
|
||||||
"userSearchResultsPerPage": 50,
|
|
||||||
"searchDefaultSortBy": "relevance",
|
|
||||||
"searchDefaultIn": "titlesposts",
|
|
||||||
"searchDefaultInQuick": "titles",
|
|
||||||
"maximumGroupNameLength": 255,
|
|
||||||
"maximumGroupTitleLength": 40,
|
|
||||||
"preventTopicDeleteAfterReplies": 0,
|
|
||||||
"feeds:disableSitemap": 0,
|
|
||||||
"feeds:disableRSS": 0,
|
|
||||||
"sitemapTopics": 500,
|
|
||||||
"maintenanceMode": 0,
|
|
||||||
"maintenanceModeStatus": 503,
|
|
||||||
"upvoteVisibility": "all",
|
|
||||||
"downvoteVisibility": "privileged",
|
|
||||||
"maximumInvites": 0,
|
|
||||||
"username:disableEdit": 0,
|
|
||||||
"email:disableEdit": 0,
|
|
||||||
"email:smtpTransport:pool": 0,
|
|
||||||
"hideFullname": 0,
|
|
||||||
"hideEmail": 0,
|
|
||||||
"showFullnameAsDisplayName": 0,
|
|
||||||
"allowGuestHandles": 0,
|
|
||||||
"guestsIncrementTopicViews": 1,
|
|
||||||
"allowGuestReplyNotifications": 1,
|
|
||||||
"incrementTopicViewsInterval": 60,
|
|
||||||
"recentMaxTopics": 200,
|
|
||||||
"disableRecentCategoryFilter": 0,
|
|
||||||
"maximumRelatedTopics": 0,
|
|
||||||
"disableEmailSubscriptions": 0,
|
|
||||||
"emailConfirmInterval": 10,
|
|
||||||
"emailConfirmExpiry": 24,
|
|
||||||
"removeEmailNotificationImages": 0,
|
|
||||||
"sendValidationEmail": 1,
|
|
||||||
"includeUnverifiedEmails": 0,
|
|
||||||
"emailPrompt": 1,
|
|
||||||
"sendEmailToBanned": 0,
|
|
||||||
"requireEmailAddress": 0,
|
|
||||||
"inviteExpiration": 7,
|
|
||||||
"dailyDigestFreq": "off",
|
|
||||||
"digestHour": 17,
|
|
||||||
"passwordExpiryDays": 0,
|
|
||||||
"cross-origin-embedder-policy": 0,
|
|
||||||
"cross-origin-opener-policy": "same-origin",
|
|
||||||
"cross-origin-resource-policy": "same-origin",
|
|
||||||
"hsts-maxage": 31536000,
|
|
||||||
"hsts-subdomains": 0,
|
|
||||||
"hsts-preload": 0,
|
|
||||||
"hsts-enabled": 0,
|
|
||||||
"eventLoopCheckEnabled": 1,
|
|
||||||
"eventLoopLagThreshold": 100,
|
|
||||||
"eventLoopInterval": 500,
|
|
||||||
"onlineCutoff": 30,
|
|
||||||
"timeagoCutoff": 30,
|
|
||||||
"necroThreshold": 7,
|
|
||||||
"categoryWatchState": "tracking",
|
|
||||||
"submitPluginUsage": 1,
|
|
||||||
"showAverageApprovalTime": 1,
|
|
||||||
"autoApproveTime": 0,
|
|
||||||
"maxUserSessions": 10,
|
|
||||||
"useCompression": 0,
|
|
||||||
"updateUrlWithPostIndex": 1,
|
|
||||||
"composer:showHelpTab": 1,
|
|
||||||
"composer:allowPluginHelp": 1,
|
|
||||||
"maxReconnectionAttempts": 5,
|
|
||||||
"reconnectionDelay": 1500,
|
|
||||||
"disableCustomUserSkins": 0,
|
|
||||||
"activitypubEnabled": 1,
|
|
||||||
"activitypubAllowLoopback": 0,
|
|
||||||
"activitypubProbe": 1,
|
|
||||||
"activitypubProbeTimeout": 2000,
|
|
||||||
"activitypubContentPruneDays": 30,
|
|
||||||
"activitypubUserPruneDays": 7,
|
|
||||||
"activitypubFilter": 0
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"widget": "html",
|
"widget": "html",
|
||||||
"data" : {
|
"data" : {
|
||||||
"html": "<footer id=\"footer\" class=\"container footer d-flex flex-column align-items-center gap-1 mb-2\">\n\t<span>Powered by <a class=\"link-secondary text-decoration-underline\" target=\"_blank\" href=\"https://nodebb.org\">NodeBB</a></span>\n\t<span><a class=\"link-secondary\" target=\"_blank\" href=\"//github.com/NodeBB/NodeBB/graphs/contributors\"><i class=\"fa fa-users\"></i> <span class=\"text-decoration-underline\">Contributors<span></a></span>\n</footer>",
|
"html": "<footer id=\"footer\" class=\"container footer\">\r\n\t<div>\r\n\t\tPowered by <a target=\"_blank\" href=\"https://nodebb.org\">NodeBB</a> | <a target=\"_blank\" href=\"//github.com/NodeBB/NodeBB/graphs/contributors\">Contributors</a>\r\n\t</div>\r\n</footer>",
|
||||||
"title":"",
|
"title":"",
|
||||||
"container":""
|
"container":""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"title": "[[global:header.categories]]",
|
"title": "[[global:header.categories]]",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconClass": "fa-list",
|
"iconClass": "fa-list",
|
||||||
"textClass": "d-lg-none",
|
"textClass": "visible-xs-inline",
|
||||||
"text": "[[global:header.categories]]"
|
"text": "[[global:header.categories]]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -13,16 +13,18 @@
|
|||||||
"title": "[[global:header.unread]]",
|
"title": "[[global:header.unread]]",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconClass": "fa-inbox",
|
"iconClass": "fa-inbox",
|
||||||
"textClass": "d-lg-none",
|
"textClass": "visible-xs-inline",
|
||||||
"text": "[[global:header.unread]]",
|
"text": "[[global:header.unread]]",
|
||||||
"groups": ["registered-users"]
|
"properties": {
|
||||||
|
"loggedIn": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"route": "/recent",
|
"route": "/recent",
|
||||||
"title": "[[global:header.recent]]",
|
"title": "[[global:header.recent]]",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconClass": "fa-clock-o",
|
"iconClass": "fa-clock-o",
|
||||||
"textClass": "d-lg-none",
|
"textClass": "visible-xs-inline",
|
||||||
"text": "[[global:header.recent]]"
|
"text": "[[global:header.recent]]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
"title": "[[global:header.tags]]",
|
"title": "[[global:header.tags]]",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconClass": "fa-tags",
|
"iconClass": "fa-tags",
|
||||||
"textClass": "d-lg-none",
|
"textClass": "visible-xs-inline",
|
||||||
"text": "[[global:header.tags]]"
|
"text": "[[global:header.tags]]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -38,23 +40,15 @@
|
|||||||
"title": "[[global:header.popular]]",
|
"title": "[[global:header.popular]]",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconClass": "fa-fire",
|
"iconClass": "fa-fire",
|
||||||
"textClass": "d-lg-none",
|
"textClass": "visible-xs-inline",
|
||||||
"text": "[[global:header.popular]]"
|
"text": "[[global:header.popular]]"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"route": "/world",
|
|
||||||
"title": "[[global:header.world]]",
|
|
||||||
"enabled": true,
|
|
||||||
"iconClass": "fa-globe",
|
|
||||||
"textClass": "d-lg-none",
|
|
||||||
"text": "[[global:header.world]]"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"route": "/users",
|
"route": "/users",
|
||||||
"title": "[[global:header.users]]",
|
"title": "[[global:header.users]]",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconClass": "fa-user",
|
"iconClass": "fa-user",
|
||||||
"textClass": "d-lg-none",
|
"textClass": "visible-xs-inline",
|
||||||
"text": "[[global:header.users]]"
|
"text": "[[global:header.users]]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -62,7 +56,7 @@
|
|||||||
"title": "[[global:header.groups]]",
|
"title": "[[global:header.groups]]",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconClass": "fa-group",
|
"iconClass": "fa-group",
|
||||||
"textClass": "d-lg-none",
|
"textClass": "visible-xs-inline",
|
||||||
"text": "[[global:header.groups]]"
|
"text": "[[global:header.groups]]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,8 +64,22 @@
|
|||||||
"title": "[[global:header.admin]]",
|
"title": "[[global:header.admin]]",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconClass": "fa-cogs",
|
"iconClass": "fa-cogs",
|
||||||
"textClass": "d-lg-none",
|
"textClass": "visible-xs-inline",
|
||||||
"text": "[[global:header.admin]]",
|
"text": "[[global:header.admin]]",
|
||||||
"groups": ["administrators"]
|
"properties": {
|
||||||
|
"targetBlank": false,
|
||||||
|
"adminOnly": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/search",
|
||||||
|
"title": "[[global:header.search]]",
|
||||||
|
"enabled": true,
|
||||||
|
"iconClass": "fa-search",
|
||||||
|
"textClass": "visible-xs-inline",
|
||||||
|
"text": "[[global:header.search]]",
|
||||||
|
"properties": {
|
||||||
|
"searchInstalled": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
### Welcome to your brand new NodeBB forum!
|
# Welcome to your brand new NodeBB forum!
|
||||||
|
|
||||||
This is what a topic and post looks like. As an administrator, you can edit the post\'s title and content.
|
This is what a topic and post looks like. As an administrator, you can edit the post\'s title and content.
|
||||||
To customise your forum, go to the [Administrator Control Panel](../../admin). You can modify all aspects of your forum there, including installation of third-party plugins.
|
To customise your forum, go to the [Administrator Control Panel](../../admin). You can modify all aspects of your forum there, including installation of third-party plugins.
|
||||||
|
|
||||||
#### Additional Resources
|
## Additional Resources
|
||||||
|
|
||||||
* [NodeBB Documentation](https://docs.nodebb.org)
|
* [NodeBB Documentation](https://docs.nodebb.org)
|
||||||
* [Community Support Forum](https://community.nodebb.org)
|
* [Community Support Forum](https://community.nodebb.org)
|
||||||
|
|||||||
@@ -1,47 +1,52 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const prompt = require('prompt');
|
var async = require('async');
|
||||||
const winston = require('winston');
|
var prompt = require('prompt');
|
||||||
|
var winston = require('winston');
|
||||||
|
|
||||||
const questions = {
|
var questions = {
|
||||||
redis: require('../src/database/redis').questions,
|
redis: require('../src/database/redis').questions,
|
||||||
mongo: require('../src/database/mongo').questions,
|
mongo: require('../src/database/mongo').questions,
|
||||||
postgres: require('../src/database/postgres').questions,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = async function (config) {
|
module.exports = function (config, callback) {
|
||||||
winston.info(`\nNow configuring ${config.database} database:`);
|
async.waterfall([
|
||||||
const databaseConfig = await getDatabaseConfig(config);
|
function (next) {
|
||||||
return saveDatabaseConfig(config, databaseConfig);
|
process.stdout.write('\n');
|
||||||
|
winston.info('Now configuring ' + config.database + ' database:');
|
||||||
|
getDatabaseConfig(config, next);
|
||||||
|
},
|
||||||
|
function (databaseConfig, next) {
|
||||||
|
saveDatabaseConfig(config, databaseConfig, next);
|
||||||
|
},
|
||||||
|
], callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getDatabaseConfig(config) {
|
function getDatabaseConfig(config, callback) {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error('invalid config, aborted');
|
return callback(new Error('aborted'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.database === 'redis') {
|
if (config.database === 'redis') {
|
||||||
if (config['redis:host'] && config['redis:port']) {
|
if (config['redis:host'] && config['redis:port']) {
|
||||||
return config;
|
callback(null, config);
|
||||||
|
} else {
|
||||||
|
prompt.get(questions.redis, callback);
|
||||||
}
|
}
|
||||||
return await prompt.get(questions.redis);
|
|
||||||
} else if (config.database === 'mongo') {
|
} else if (config.database === 'mongo') {
|
||||||
if ((config['mongo:host'] && config['mongo:port']) || config['mongo:uri']) {
|
if (config['mongo:host'] && config['mongo:port']) {
|
||||||
return config;
|
callback(null, config);
|
||||||
|
} else {
|
||||||
|
prompt.get(questions.mongo, callback);
|
||||||
}
|
}
|
||||||
return await prompt.get(questions.mongo);
|
} else {
|
||||||
} else if (config.database === 'postgres') {
|
return callback(new Error('unknown database : ' + config.database));
|
||||||
if (config['postgres:host'] && config['postgres:port']) {
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
return await prompt.get(questions.postgres);
|
|
||||||
}
|
|
||||||
throw new Error(`unknown database : ${config.database}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveDatabaseConfig(config, databaseConfig) {
|
function saveDatabaseConfig(config, databaseConfig, callback) {
|
||||||
if (!databaseConfig) {
|
if (!databaseConfig) {
|
||||||
throw new Error('invalid config, aborted');
|
return callback(new Error('aborted'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate redis properties into redis object
|
// Translate redis properties into redis object
|
||||||
@@ -63,25 +68,15 @@ function saveDatabaseConfig(config, databaseConfig) {
|
|||||||
username: databaseConfig['mongo:username'],
|
username: databaseConfig['mongo:username'],
|
||||||
password: databaseConfig['mongo:password'],
|
password: databaseConfig['mongo:password'],
|
||||||
database: databaseConfig['mongo:database'],
|
database: databaseConfig['mongo:database'],
|
||||||
uri: databaseConfig['mongo:uri'],
|
|
||||||
};
|
|
||||||
} else if (config.database === 'postgres') {
|
|
||||||
config.postgres = {
|
|
||||||
host: databaseConfig['postgres:host'],
|
|
||||||
port: databaseConfig['postgres:port'],
|
|
||||||
username: databaseConfig['postgres:username'],
|
|
||||||
password: databaseConfig['postgres:password'],
|
|
||||||
database: databaseConfig['postgres:database'],
|
|
||||||
ssl: databaseConfig['postgres:ssl'],
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`unknown database : ${config.database}`);
|
return callback(new Error('unknown database : ' + config.database));
|
||||||
}
|
}
|
||||||
|
|
||||||
const allQuestions = questions.redis.concat(questions.mongo).concat(questions.postgres);
|
var allQuestions = questions.redis.concat(questions.mongo);
|
||||||
for (let x = 0; x < allQuestions.length; x += 1) {
|
for (var x = 0; x < allQuestions.length; x += 1) {
|
||||||
delete config[allQuestions[x].name];
|
delete config[allQuestions[x].name];
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
callback(null, config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Function to set default values for environment variables
|
|
||||||
set_defaults() {
|
|
||||||
export CONFIG_DIR="${CONFIG_DIR:-/opt/config}"
|
|
||||||
export CONFIG="$CONFIG_DIR/config.json"
|
|
||||||
export NODEBB_INIT_VERB="${NODEBB_INIT_VERB:-install}"
|
|
||||||
export NODEBB_BUILD_VERB="${NODEBB_BUILD_VERB:-build}"
|
|
||||||
export START_BUILD="${START_BUILD:-${FORCE_BUILD_BEFORE_START:-false}}"
|
|
||||||
export SETUP="${SETUP:-}"
|
|
||||||
export PACKAGE_MANAGER="${PACKAGE_MANAGER:-npm}"
|
|
||||||
export OVERRIDE_UPDATE_LOCK="${OVERRIDE_UPDATE_LOCK:-false}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to check if a directory exists and is writable
|
|
||||||
check_directory() {
|
|
||||||
local dir="$1"
|
|
||||||
if [ ! -d "$dir" ]; then
|
|
||||||
echo "Error: Directory $dir does not exist. Creating..."
|
|
||||||
mkdir -p "$dir" || {
|
|
||||||
echo "Error: Failed to create directory $dir"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
if [ ! -w "$dir" ]; then
|
|
||||||
echo "Warning: No write permission for directory $dir, attempting to fix..."
|
|
||||||
chown -R $USER:$USER "$dir" || true # attempt to change ownership, do not exit on failure
|
|
||||||
chmod -R 760 "$dir" || true # attempt to change permissions, do not exit on failure
|
|
||||||
if [ ! -w "$dir" ]; then
|
|
||||||
echo "Error: No write permission for directory $dir. Exiting..."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to copy or link package.json and lock files based on package manager
|
|
||||||
copy_or_link_files() {
|
|
||||||
local src_dir="$1"
|
|
||||||
local dest_dir="$2"
|
|
||||||
local package_manager="$3"
|
|
||||||
local lock_file
|
|
||||||
|
|
||||||
case "$package_manager" in
|
|
||||||
yarn) lock_file="yarn.lock" ;;
|
|
||||||
npm) lock_file="package-lock.json" ;;
|
|
||||||
pnpm) lock_file="pnpm-lock.yaml" ;;
|
|
||||||
*)
|
|
||||||
echo "Unknown package manager: $package_manager"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Check if source and destination files are the same
|
|
||||||
if [ "$(realpath "$src_dir/package.json")" != "$(realpath "$dest_dir/package.json")" ] || [ "$OVERRIDE_UPDATE_LOCK" = true ]; then
|
|
||||||
cp "$src_dir/package.json" "$dest_dir/package.json"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$(realpath "$src_dir/$lock_file")" != "$(realpath "$dest_dir/$lock_file")" ] || [ "$OVERRIDE_UPDATE_LOCK" = true ]; then
|
|
||||||
cp "$src_dir/$lock_file" "$dest_dir/$lock_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove unnecessary lock files in src_dir
|
|
||||||
rm -f "$src_dir/"{yarn.lock,package-lock.json,pnpm-lock.yaml}
|
|
||||||
|
|
||||||
# Symbolically link the copied files in src_dir to dest_dir
|
|
||||||
ln -fs "$dest_dir/package.json" "$src_dir/package.json"
|
|
||||||
ln -fs "$dest_dir/$lock_file" "$src_dir/$lock_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to install dependencies using pnpm
|
|
||||||
install_dependencies() {
|
|
||||||
case "$PACKAGE_MANAGER" in
|
|
||||||
yarn) yarn install || {
|
|
||||||
echo "Failed to install dependencies with yarn"
|
|
||||||
exit 1
|
|
||||||
} ;;
|
|
||||||
npm) npm install || {
|
|
||||||
echo "Failed to install dependencies with npm"
|
|
||||||
exit 1
|
|
||||||
} ;;
|
|
||||||
pnpm) pnpm install || {
|
|
||||||
echo "Failed to install dependencies with pnpm"
|
|
||||||
exit 1
|
|
||||||
} ;;
|
|
||||||
*)
|
|
||||||
echo "Unknown package manager: $PACKAGE_MANAGER"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to start setup session
|
|
||||||
start_setup_session() {
|
|
||||||
local config="$1"
|
|
||||||
echo "Starting setup session"
|
|
||||||
exec /usr/src/app/nodebb setup --config="$config"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Handle building and upgrading NodeBB
|
|
||||||
build_forum() {
|
|
||||||
local config="$1"
|
|
||||||
local start_build="$2"
|
|
||||||
local package_hash=$(md5sum install/package.json | head -c 32)
|
|
||||||
if [ "$package_hash" = "$(cat $CONFIG_DIR/install_hash.md5 || true)" ]; then
|
|
||||||
echo "package.json was updated. Upgrading..."
|
|
||||||
/usr/src/app/nodebb upgrade --config="$config" || {
|
|
||||||
echo "Failed to build NodeBB. Exiting..."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
elif [ "$start_build" = true ]; then
|
|
||||||
echo "Build before start is enabled. Building..."
|
|
||||||
/usr/src/app/nodebb "${NODEBB_BUILD_VERB}" --config="$config" || {
|
|
||||||
echo "Failed to build NodeBB. Exiting..."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
else
|
|
||||||
echo "No changes in package.json. Skipping build..."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
echo -n $package_hash > $CONFIG_DIR/install_hash.md5
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Function to start forum
|
|
||||||
start_forum() {
|
|
||||||
local config="$1"
|
|
||||||
local start_build="$2"
|
|
||||||
|
|
||||||
build_forum "$config" "$start_build"
|
|
||||||
|
|
||||||
case "$PACKAGE_MANAGER" in
|
|
||||||
yarn)
|
|
||||||
yarn start --config="$config" --no-silent --no-daemon || {
|
|
||||||
echo "Failed to start forum with yarn"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
;;
|
|
||||||
npm)
|
|
||||||
npm start -- --config="$config" --no-silent --no-daemon || {
|
|
||||||
echo "Failed to start forum with npm"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
;;
|
|
||||||
pnpm)
|
|
||||||
pnpm start -- --config="$config" --no-silent --no-daemon || {
|
|
||||||
echo "Failed to start forum with pnpm"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown package manager: $PACKAGE_MANAGER"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to start installation session
|
|
||||||
start_installation_session() {
|
|
||||||
local nodebb_init_verb="$1"
|
|
||||||
local config="$2"
|
|
||||||
|
|
||||||
echo "Config file not found at $config"
|
|
||||||
echo "Starting installation session"
|
|
||||||
exec /usr/src/app/nodebb "$nodebb_init_verb" --config="$config"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function for debugging and logging
|
|
||||||
debug_log() {
|
|
||||||
local message="$1"
|
|
||||||
echo "DEBUG: $message"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main function
|
|
||||||
main() {
|
|
||||||
set_defaults
|
|
||||||
check_directory "$CONFIG_DIR"
|
|
||||||
copy_or_link_files /usr/src/app "$CONFIG_DIR" "$PACKAGE_MANAGER"
|
|
||||||
install_dependencies
|
|
||||||
|
|
||||||
debug_log "PACKAGE_MANAGER: $PACKAGE_MANAGER"
|
|
||||||
debug_log "CONFIG location: $CONFIG"
|
|
||||||
debug_log "START_BUILD: $START_BUILD"
|
|
||||||
|
|
||||||
if [ -n "$SETUP" ]; then
|
|
||||||
start_setup_session "$CONFIG"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$CONFIG" ]; then
|
|
||||||
start_forum "$CONFIG" "$START_BUILD"
|
|
||||||
else
|
|
||||||
start_installation_session "$NODEBB_INIT_VERB" "$CONFIG"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Execute main function
|
|
||||||
main "$@"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
db.createUser( { user: 'nodebb', pwd: 'nodebb', roles: [ { role: 'readWrite', db: 'nodebb' }, { role: 'clusterMonitor', db: 'admin' } ] } )
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"defaults": {
|
|
||||||
"mongo": {
|
|
||||||
"host": "mongo",
|
|
||||||
"port": 27017,
|
|
||||||
"database": "nodebb",
|
|
||||||
"username": "nodebb",
|
|
||||||
"password": "nodebb"
|
|
||||||
},
|
|
||||||
"redis": {
|
|
||||||
"host": "redis",
|
|
||||||
"port": 6379,
|
|
||||||
"database": 0
|
|
||||||
},
|
|
||||||
"postgres": {
|
|
||||||
"host": "postgres",
|
|
||||||
"port": 5432,
|
|
||||||
"database": "nodebb",
|
|
||||||
"username": "nodebb",
|
|
||||||
"password": "nodebb"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "nodebb",
|
|
||||||
"license": "GPL-3.0",
|
|
||||||
"description": "NodeBB Forum",
|
|
||||||
"version": "4.1.0",
|
|
||||||
"homepage": "https://www.nodebb.org",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/NodeBB/NodeBB/"
|
|
||||||
},
|
|
||||||
"main": "app.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node loader.js",
|
|
||||||
"lint": "eslint --cache ./nodebb .",
|
|
||||||
"test": "nyc --reporter=html --reporter=text-summary mocha",
|
|
||||||
"coverage": "nyc report --reporter=text-lcov > ./coverage/lcov.info",
|
|
||||||
"coveralls": "nyc report --reporter=text-lcov | coveralls && rm -r coverage"
|
|
||||||
},
|
|
||||||
"nyc": {
|
|
||||||
"exclude": [
|
|
||||||
"src/upgrades/*",
|
|
||||||
"test/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"*.js": [
|
|
||||||
"eslint --fix"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@adactive/bootstrap-tagsinput": "0.8.2",
|
|
||||||
"@fontsource/inter": "5.1.1",
|
|
||||||
"@fontsource/poppins": "5.1.1",
|
|
||||||
"@fortawesome/fontawesome-free": "6.7.2",
|
|
||||||
"@isaacs/ttlcache": "1.4.1",
|
|
||||||
"@nodebb/spider-detector": "2.0.3",
|
|
||||||
"@popperjs/core": "2.11.8",
|
|
||||||
"@resvg/resvg-js": "2.6.2",
|
|
||||||
"@textcomplete/contenteditable": "0.1.13",
|
|
||||||
"@textcomplete/core": "0.1.13",
|
|
||||||
"@textcomplete/textarea": "0.1.13",
|
|
||||||
"ace-builds": "1.39.0",
|
|
||||||
"archiver": "7.0.1",
|
|
||||||
"async": "3.2.6",
|
|
||||||
"autoprefixer": "10.4.20",
|
|
||||||
"bcryptjs": "3.0.2",
|
|
||||||
"benchpressjs": "2.5.3",
|
|
||||||
"body-parser": "1.20.3",
|
|
||||||
"bootbox": "6.0.0",
|
|
||||||
"bootstrap": "5.3.3",
|
|
||||||
"bootswatch": "5.3.3",
|
|
||||||
"chalk": "4.1.2",
|
|
||||||
"chart.js": "4.4.8",
|
|
||||||
"cli-graph": "3.2.2",
|
|
||||||
"clipboard": "2.0.11",
|
|
||||||
"colors": "1.4.0",
|
|
||||||
"commander": "12.1.0",
|
|
||||||
"compare-versions": "6.1.1",
|
|
||||||
"compression": "1.8.0",
|
|
||||||
"connect-flash": "0.1.1",
|
|
||||||
"connect-mongo": "5.1.0",
|
|
||||||
"connect-multiparty": "2.2.0",
|
|
||||||
"connect-pg-simple": "10.0.0",
|
|
||||||
"connect-redis": "8.0.1",
|
|
||||||
"cookie-parser": "1.4.7",
|
|
||||||
"cron": "4.1.0",
|
|
||||||
"cropperjs": "1.6.2",
|
|
||||||
"csrf-sync": "4.0.3",
|
|
||||||
"daemon": "1.1.0",
|
|
||||||
"diff": "7.0.0",
|
|
||||||
"esbuild": "0.25.0",
|
|
||||||
"express": "4.21.2",
|
|
||||||
"express-session": "1.18.1",
|
|
||||||
"express-useragent": "1.0.15",
|
|
||||||
"fetch-cookie": "3.1.0",
|
|
||||||
"file-loader": "6.2.0",
|
|
||||||
"fs-extra": "11.3.0",
|
|
||||||
"graceful-fs": "4.2.11",
|
|
||||||
"helmet": "7.2.0",
|
|
||||||
"html-to-text": "9.0.5",
|
|
||||||
"imagesloaded": "5.0.0",
|
|
||||||
"ipaddr.js": "2.2.0",
|
|
||||||
"jquery": "3.7.1",
|
|
||||||
"jquery-deserialize": "2.0.0",
|
|
||||||
"jquery-form": "4.3.0",
|
|
||||||
"jquery-serializeobject": "1.0.0",
|
|
||||||
"jquery-ui": "1.14.1",
|
|
||||||
"jsesc": "3.1.0",
|
|
||||||
"json2csv": "5.0.7",
|
|
||||||
"jsonwebtoken": "9.0.2",
|
|
||||||
"lodash": "4.17.21",
|
|
||||||
"logrotate-stream": "0.2.9",
|
|
||||||
"lru-cache": "10.4.3",
|
|
||||||
"mime": "3.0.0",
|
|
||||||
"mkdirp": "3.0.1",
|
|
||||||
"mongodb": "6.14.0",
|
|
||||||
"morgan": "1.10.0",
|
|
||||||
"mousetrap": "1.6.5",
|
|
||||||
"multiparty": "4.2.3",
|
|
||||||
"nconf": "0.12.1",
|
|
||||||
"nodebb-plugin-2factor": "7.5.9",
|
|
||||||
"nodebb-plugin-composer-default": "10.2.47",
|
|
||||||
"nodebb-plugin-dbsearch": "6.2.13",
|
|
||||||
"nodebb-plugin-emoji": "6.0.2",
|
|
||||||
"nodebb-plugin-emoji-android": "4.1.1",
|
|
||||||
"nodebb-plugin-markdown": "13.1.1",
|
|
||||||
"nodebb-plugin-mentions": "4.7.1",
|
|
||||||
"nodebb-plugin-spam-be-gone": "2.3.1",
|
|
||||||
"nodebb-plugin-web-push": "0.7.3",
|
|
||||||
"nodebb-rewards-essentials": "1.0.1",
|
|
||||||
"nodebb-theme-harmony": "2.0.38",
|
|
||||||
"nodebb-theme-lavender": "7.1.17",
|
|
||||||
"nodebb-theme-peace": "2.2.39",
|
|
||||||
"nodebb-theme-persona": "14.0.15",
|
|
||||||
"nodebb-widget-essentials": "7.0.35",
|
|
||||||
"nodemailer": "6.10.0",
|
|
||||||
"nprogress": "0.2.0",
|
|
||||||
"passport": "0.7.0",
|
|
||||||
"passport-http-bearer": "1.0.1",
|
|
||||||
"passport-local": "1.0.0",
|
|
||||||
"pg": "8.13.3",
|
|
||||||
"pg-cursor": "2.12.3",
|
|
||||||
"postcss": "8.5.3",
|
|
||||||
"postcss-clean": "1.2.0",
|
|
||||||
"progress-webpack-plugin": "1.0.16",
|
|
||||||
"prompt": "1.3.0",
|
|
||||||
"ioredis": "5.5.0",
|
|
||||||
"rimraf": "5.0.10",
|
|
||||||
"rss": "1.2.2",
|
|
||||||
"rtlcss": "4.3.0",
|
|
||||||
"sanitize-html": "2.14.0",
|
|
||||||
"sass": "1.85.1",
|
|
||||||
"satori": "0.12.1",
|
|
||||||
"semver": "7.7.1",
|
|
||||||
"serve-favicon": "2.5.0",
|
|
||||||
"sharp": "0.32.6",
|
|
||||||
"sitemap": "8.0.0",
|
|
||||||
"socket.io": "4.8.1",
|
|
||||||
"socket.io-client": "4.8.1",
|
|
||||||
"@socket.io/redis-adapter": "8.3.0",
|
|
||||||
"sortablejs": "1.15.6",
|
|
||||||
"spdx-license-list": "6.9.0",
|
|
||||||
"terser-webpack-plugin": "5.3.12",
|
|
||||||
"textcomplete": "0.18.2",
|
|
||||||
"textcomplete.contenteditable": "0.1.1",
|
|
||||||
"timeago": "1.6.7",
|
|
||||||
"tinycon": "0.6.8",
|
|
||||||
"toobusy-js": "0.5.1",
|
|
||||||
"tough-cookie": "5.1.2",
|
|
||||||
"validator": "13.12.0",
|
|
||||||
"webpack": "5.98.0",
|
|
||||||
"webpack-merge": "6.0.1",
|
|
||||||
"winston": "3.17.0",
|
|
||||||
"workerpool": "9.2.0",
|
|
||||||
"xml": "1.0.1",
|
|
||||||
"xregexp": "5.1.2",
|
|
||||||
"yargs": "17.7.2",
|
|
||||||
"zxcvbn": "4.4.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@apidevtools/swagger-parser": "10.1.0",
|
|
||||||
"@commitlint/cli": "19.7.1",
|
|
||||||
"@commitlint/config-angular": "19.7.1",
|
|
||||||
"coveralls": "3.1.1",
|
|
||||||
"eslint": "8.57.1",
|
|
||||||
"eslint-config-nodebb": "0.2.1",
|
|
||||||
"eslint-plugin-import": "2.31.0",
|
|
||||||
"grunt": "1.6.1",
|
|
||||||
"grunt-contrib-watch": "1.1.0",
|
|
||||||
"husky": "8.0.3",
|
|
||||||
"jsdom": "26.0.0",
|
|
||||||
"lint-staged": "15.4.3",
|
|
||||||
"mocha": "11.1.0",
|
|
||||||
"mocha-lcov-reporter": "1.3.0",
|
|
||||||
"mockdate": "3.0.5",
|
|
||||||
"nyc": "17.1.0",
|
|
||||||
"smtp-server": "3.13.6"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"sass-embedded": "1.85.1"
|
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"*/jquery": "3.7.1"
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/NodeBB/NodeBB/issues"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"maintainers": [
|
|
||||||
{
|
|
||||||
"name": "Julian Lam",
|
|
||||||
"email": "julian@nodebb.org",
|
|
||||||
"url": "https://github.com/julianlam"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Barış Soner Uşaklı",
|
|
||||||
"email": "baris@nodebb.org",
|
|
||||||
"url": "https://github.com/barisusakli"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
412
install/web.js
@@ -1,331 +1,197 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const winston = require('winston');
|
var winston = require('winston');
|
||||||
const express = require('express');
|
var express = require('express');
|
||||||
const session = require('express-session');
|
var bodyParser = require('body-parser');
|
||||||
const bodyParser = require('body-parser');
|
var fs = require('fs');
|
||||||
const fs = require('fs');
|
var path = require('path');
|
||||||
const path = require('path');
|
var less = require('less');
|
||||||
const childProcess = require('child_process');
|
var async = require('async');
|
||||||
|
var uglify = require('uglify-js');
|
||||||
|
var nconf = require('nconf');
|
||||||
|
var Benchpress = require('benchpressjs');
|
||||||
|
|
||||||
const webpack = require('webpack');
|
var app = express();
|
||||||
const nconf = require('nconf');
|
var server;
|
||||||
|
|
||||||
const Benchpress = require('benchpressjs');
|
winston.add(winston.transports.File, {
|
||||||
const { mkdirp } = require('mkdirp');
|
filename: 'logs/webinstall.log',
|
||||||
const { paths } = require('../src/constants');
|
colorize: true,
|
||||||
const utils = require('../src/utils');
|
timestamp: function () {
|
||||||
|
var date = new Date();
|
||||||
|
return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0, 5) + ' [' + global.process.pid + ']';
|
||||||
|
},
|
||||||
|
level: 'verbose',
|
||||||
|
});
|
||||||
|
|
||||||
const sass = utils.getSass();
|
var web = {};
|
||||||
const { generateToken, csrfSynchronisedProtection } = require('../src/middleware/csrf');
|
var scripts = [
|
||||||
|
'public/vendor/xregexp/xregexp.js',
|
||||||
const app = express();
|
'public/vendor/xregexp/unicode/unicode-base.js',
|
||||||
let server;
|
'public/src/utils.js',
|
||||||
|
'public/src/installer/install.js',
|
||||||
const formats = [
|
|
||||||
winston.format.colorize(),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const timestampFormat = winston.format((info) => {
|
web.install = function (port) {
|
||||||
const dateString = `${new Date().toISOString()} [${global.process.pid}]`;
|
|
||||||
info.level = `${dateString} - ${info.level}`;
|
|
||||||
return info;
|
|
||||||
});
|
|
||||||
formats.push(timestampFormat());
|
|
||||||
formats.push(winston.format.splat());
|
|
||||||
formats.push(winston.format.simple());
|
|
||||||
|
|
||||||
winston.configure({
|
|
||||||
level: 'verbose',
|
|
||||||
format: winston.format.combine.apply(null, formats),
|
|
||||||
transports: [
|
|
||||||
new winston.transports.Console({
|
|
||||||
handleExceptions: true,
|
|
||||||
}),
|
|
||||||
new winston.transports.File({
|
|
||||||
filename: 'logs/webinstall.log',
|
|
||||||
handleExceptions: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const web = module.exports;
|
|
||||||
let installing = false;
|
|
||||||
let success = false;
|
|
||||||
let error = false;
|
|
||||||
let launchUrl;
|
|
||||||
let timeStart = 0;
|
|
||||||
const totalTime = 1000 * 60 * 3;
|
|
||||||
|
|
||||||
|
|
||||||
const viewsDir = path.join(paths.baseDir, 'build/public/templates');
|
|
||||||
|
|
||||||
web.install = async function (port) {
|
|
||||||
port = port || 4567;
|
port = port || 4567;
|
||||||
winston.info(`Launching web installer on port ${port}`);
|
winston.info('Launching web installer on port', port);
|
||||||
|
|
||||||
app.use(express.static('public', {}));
|
app.use(express.static('public', {}));
|
||||||
app.use('/assets', express.static(path.join(__dirname, '../build/public'), {}));
|
app.engine('tpl', function (filepath, options, callback) {
|
||||||
|
async.waterfall([
|
||||||
app.engine('tpl', (filepath, options, callback) => {
|
function (next) {
|
||||||
filepath = filepath.replace(/\.tpl$/, '.js');
|
fs.readFile(filepath, 'utf-8', next);
|
||||||
|
},
|
||||||
Benchpress.__express(filepath, options, callback);
|
function (buffer, next) {
|
||||||
|
Benchpress.compileParse(buffer.toString(), options, next);
|
||||||
|
},
|
||||||
|
], callback);
|
||||||
});
|
});
|
||||||
app.set('view engine', 'tpl');
|
app.set('view engine', 'tpl');
|
||||||
app.set('views', viewsDir);
|
app.set('views', path.join(__dirname, '../src/views'));
|
||||||
app.use(bodyParser.urlencoded({
|
app.use(bodyParser.urlencoded({
|
||||||
extended: true,
|
extended: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.use(session({
|
async.parallel([compileLess, compileJS], function (err) {
|
||||||
secret: utils.generateUUID(),
|
if (err) {
|
||||||
resave: false,
|
winston.error(err);
|
||||||
saveUninitialized: false,
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Promise.all([
|
|
||||||
compileTemplate(),
|
|
||||||
compileSass(),
|
|
||||||
runWebpack(),
|
|
||||||
copyCSS(),
|
|
||||||
loadDefaults(),
|
|
||||||
]);
|
|
||||||
setupRoutes();
|
setupRoutes();
|
||||||
launchExpress(port);
|
launchExpress(port);
|
||||||
} catch (err) {
|
});
|
||||||
winston.error(err.stack);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function runWebpack() {
|
|
||||||
const util = require('util');
|
|
||||||
const webpackCfg = require('../webpack.installer');
|
|
||||||
const compiler = webpack(webpackCfg);
|
|
||||||
const webpackRun = util.promisify(compiler.run).bind(compiler);
|
|
||||||
await webpackRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
function launchExpress(port) {
|
function launchExpress(port) {
|
||||||
server = app.listen(port, () => {
|
server = app.listen(port, function () {
|
||||||
winston.info('Web installer listening on http://%s:%s', '0.0.0.0', port);
|
winston.info('Web installer listening on http://%s:%s', '0.0.0.0', port);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupRoutes() {
|
function setupRoutes() {
|
||||||
app.get('/', csrfSynchronisedProtection, welcome);
|
app.get('/', welcome);
|
||||||
app.post('/', csrfSynchronisedProtection, install);
|
app.post('/', install);
|
||||||
app.get('/testdb', testDatabase);
|
app.post('/launch', launch);
|
||||||
app.get('/ping', ping);
|
|
||||||
app.get('/sping', ping);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testDatabase(req, res) {
|
|
||||||
let db;
|
|
||||||
try {
|
|
||||||
const keys = Object.keys(req.query);
|
|
||||||
const dbName = keys[0].split(':')[0];
|
|
||||||
db = require(`../src/database/${dbName}`);
|
|
||||||
|
|
||||||
const opts = {};
|
|
||||||
keys.forEach((key) => {
|
|
||||||
opts[key.replace(`${dbName}:`, '')] = req.query[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.init(opts);
|
|
||||||
const global = await db.getObject('global');
|
|
||||||
await db.close();
|
|
||||||
res.json({ success: 1, dbfull: !!global });
|
|
||||||
} catch (err) {
|
|
||||||
res.json({ error: err.stack });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ping(req, res) {
|
|
||||||
res.status(200).send(req.path === '/sping' ? 'healthy' : '200');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function welcome(req, res) {
|
function welcome(req, res) {
|
||||||
const dbs = ['mongo', 'redis', 'postgres'];
|
var dbs = ['redis', 'mongo'];
|
||||||
const databases = dbs.map((databaseName) => {
|
var databases = dbs.map(function (el) {
|
||||||
const questions = require(`../src/database/${databaseName}`).questions.filter(question => question && !question.hideOnWebInstall);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: databaseName,
|
name: el,
|
||||||
questions: questions,
|
questions: require('../src/database/' + el).questions,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaults = require('./data/defaults.json');
|
var defaults = require('./data/defaults');
|
||||||
|
|
||||||
res.render('install/index', {
|
res.render('install/index', {
|
||||||
url: nconf.get('url') || (`${req.protocol}://${req.get('host')}`),
|
|
||||||
launchUrl: launchUrl,
|
|
||||||
skipGeneralSetup: !!nconf.get('url'),
|
|
||||||
databases: databases,
|
databases: databases,
|
||||||
skipDatabaseSetup: !!nconf.get('database'),
|
skipDatabaseSetup: !!nconf.get('database'),
|
||||||
error: error,
|
error: !!res.locals.error,
|
||||||
success: success,
|
success: !!res.locals.success,
|
||||||
values: req.body,
|
values: req.body,
|
||||||
minimumPasswordLength: defaults.minimumPasswordLength,
|
minimumPasswordLength: defaults.minimumPasswordLength,
|
||||||
minimumPasswordStrength: defaults.minimumPasswordStrength,
|
|
||||||
installing: installing,
|
|
||||||
percentInstalled: installing ? ((Date.now() - timeStart) / totalTime * 100).toFixed(2) : 0,
|
|
||||||
csrf_token: generateToken(req),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function install(req, res) {
|
function install(req, res) {
|
||||||
if (installing) {
|
for (var i in req.body) {
|
||||||
return welcome(req, res);
|
if (req.body.hasOwnProperty(i) && !process.env.hasOwnProperty(i)) {
|
||||||
|
process.env[i.replace(':', '__')] = req.body[i];
|
||||||
}
|
}
|
||||||
timeStart = Date.now();
|
|
||||||
req.setTimeout(0);
|
|
||||||
installing = true;
|
|
||||||
|
|
||||||
const database = nconf.get('database') || req.body.database || 'mongo';
|
|
||||||
const setupEnvVars = {
|
|
||||||
...process.env,
|
|
||||||
CONFIG: nconf.get('config'),
|
|
||||||
NODEBB_CONFIG: nconf.get('config'),
|
|
||||||
NODEBB_URL: nconf.get('url') || req.body.url || (`${req.protocol}://${req.get('host')}`),
|
|
||||||
NODEBB_PORT: nconf.get('port') || 4567,
|
|
||||||
NODEBB_ADMIN_USERNAME: nconf.get('admin:username') || req.body['admin:username'],
|
|
||||||
NODEBB_ADMIN_PASSWORD: nconf.get('admin:password') || req.body['admin:password'],
|
|
||||||
NODEBB_ADMIN_EMAIL: nconf.get('admin:email') || req.body['admin:email'],
|
|
||||||
NODEBB_DB: database,
|
|
||||||
NODEBB_DB_HOST: nconf.get(`${database}:host`) || req.body[`${database}:host`],
|
|
||||||
NODEBB_DB_PORT: nconf.get(`${database}:port`) || req.body[`${database}:port`],
|
|
||||||
NODEBB_DB_USER: nconf.get(`${database}:username`) || req.body[`${database}:username`],
|
|
||||||
NODEBB_DB_PASSWORD: nconf.get(`${database}:password`) || req.body[`${database}:password`],
|
|
||||||
NODEBB_DB_NAME: nconf.get(`${database}:database`) || req.body[`${database}:database`],
|
|
||||||
NODEBB_DB_SSL: nconf.get(`${database}:ssl`) || req.body[`${database}:ssl`],
|
|
||||||
defaultPlugins: JSON.stringify(nconf.get('defaultplugins') || nconf.get('defaultPlugins') || []),
|
|
||||||
};
|
|
||||||
|
|
||||||
winston.info('Starting setup process');
|
|
||||||
launchUrl = setupEnvVars.NODEBB_URL;
|
|
||||||
|
|
||||||
const child = require('child_process').fork('app', ['--setup'], {
|
|
||||||
env: setupEnvVars,
|
|
||||||
});
|
|
||||||
child.on('error', (err) => {
|
|
||||||
error = true;
|
|
||||||
success = false;
|
|
||||||
winston.error(err.stack);
|
|
||||||
});
|
|
||||||
child.on('close', (data) => {
|
|
||||||
success = data === 0;
|
|
||||||
error = data !== 0;
|
|
||||||
launch();
|
|
||||||
});
|
|
||||||
welcome(req, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function launch() {
|
var child = require('child_process').fork('app', ['--setup'], {
|
||||||
try {
|
env: process.env,
|
||||||
server.close();
|
|
||||||
let child;
|
|
||||||
|
|
||||||
if (!nconf.get('launchCmd')) {
|
|
||||||
child = childProcess.spawn('node', ['loader.js'], {
|
|
||||||
detached: true,
|
|
||||||
stdio: ['ignore', 'ignore', 'ignore'],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('\nStarting NodeBB');
|
child.on('close', function (data) {
|
||||||
console.log(' "./nodebb stop" to stop the NodeBB server');
|
if (data === 0) {
|
||||||
console.log(' "./nodebb log" to view server output');
|
res.locals.success = true;
|
||||||
console.log(' "./nodebb restart" to restart NodeBB');
|
|
||||||
} else {
|
} else {
|
||||||
// Use launchCmd instead, if specified
|
res.locals.error = true;
|
||||||
child = childProcess.exec(nconf.get('launchCmd'), {
|
}
|
||||||
detached: true,
|
|
||||||
stdio: ['ignore', 'ignore', 'ignore'],
|
welcome(req, res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesToDelete = [
|
function launch(req, res) {
|
||||||
path.join(__dirname, '../public', 'installer.css'),
|
res.json({});
|
||||||
path.join(__dirname, '../public', 'bootstrap.min.css'),
|
server.close();
|
||||||
path.join(__dirname, '../build/public', 'installer.min.js'),
|
|
||||||
];
|
var child = require('child_process').spawn('node', ['loader.js'], {
|
||||||
try {
|
detached: true,
|
||||||
await Promise.all(
|
stdio: ['ignore', 'ignore', 'ignore'],
|
||||||
filesToDelete.map(
|
});
|
||||||
filename => fs.promises.unlink(filename)
|
|
||||||
)
|
process.stdout.write('\nStarting NodeBB\n');
|
||||||
);
|
process.stdout.write(' "./nodebb stop" to stop the NodeBB server\n');
|
||||||
} catch (err) {
|
process.stdout.write(' "./nodebb log" to view server output\n');
|
||||||
console.log(err.stack);
|
process.stdout.write(' "./nodebb restart" to restart NodeBB\n');
|
||||||
|
|
||||||
|
async.parallel([
|
||||||
|
function (next) {
|
||||||
|
fs.unlink(path.join(__dirname, '../public/installer.css'), next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
fs.unlink(path.join(__dirname, '../public/installer.min.js'), next);
|
||||||
|
},
|
||||||
|
], function (err) {
|
||||||
|
if (err) {
|
||||||
|
winston.warn('Unable to remove installer files');
|
||||||
}
|
}
|
||||||
|
|
||||||
child.unref();
|
child.unref();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (err) {
|
|
||||||
winston.error(err.stack);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is necessary because otherwise the compiled templates won't be available on a clean install
|
|
||||||
async function compileTemplate() {
|
|
||||||
const sourceFile = path.join(__dirname, '../src/views/install/index.tpl');
|
|
||||||
const destTpl = path.join(viewsDir, 'install/index.tpl');
|
|
||||||
const destJs = path.join(viewsDir, 'install/index.js');
|
|
||||||
|
|
||||||
const source = await fs.promises.readFile(sourceFile, 'utf8');
|
|
||||||
|
|
||||||
const [compiled] = await Promise.all([
|
|
||||||
Benchpress.precompile(source, { filename: 'install/index.tpl' }),
|
|
||||||
mkdirp(path.dirname(destJs)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
fs.promises.writeFile(destJs, compiled),
|
|
||||||
fs.promises.writeFile(destTpl, source),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function compileSass() {
|
|
||||||
try {
|
|
||||||
const installSrc = path.join(__dirname, '../public/scss/install.scss');
|
|
||||||
const style = await fs.promises.readFile(installSrc);
|
|
||||||
const scssOutput = sass.compileString(String(style), {
|
|
||||||
loadPaths: [
|
|
||||||
path.join(__dirname, '../public/scss'),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.promises.writeFile(path.join(__dirname, '../public/installer.css'), scssOutput.css.toString());
|
|
||||||
} catch (err) {
|
|
||||||
winston.error(`Unable to compile SASS: \n${err.stack}`);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyCSS() {
|
|
||||||
await fs.promises.copyFile(
|
|
||||||
path.join(__dirname, '../node_modules/bootstrap/dist/css/bootstrap.min.css'),
|
|
||||||
path.join(__dirname, '../public/bootstrap.min.css'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadDefaults() {
|
|
||||||
const setupDefaultsPath = path.join(__dirname, '../setup.json');
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
await fs.promises.access(setupDefaultsPath, fs.constants.F_OK | fs.constants.R_OK);
|
|
||||||
} catch (err) {
|
|
||||||
// setup.json not found or inaccessible, proceed with no defaults
|
|
||||||
if (err.code !== 'ENOENT') {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
winston.info('[installer] Found setup.json, populating default values');
|
|
||||||
nconf.file({
|
|
||||||
file: setupDefaultsPath,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compileLess(callback) {
|
||||||
|
fs.readFile(path.join(__dirname, '../public/less/install.less'), function (err, style) {
|
||||||
|
if (err) {
|
||||||
|
return winston.error('Unable to read LESS install file: ', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
less.render(style.toString(), function (err, css) {
|
||||||
|
if (err) {
|
||||||
|
return winston.error('Unable to compile LESS: ', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFile(path.join(__dirname, '../public/installer.css'), css.css, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileJS(callback) {
|
||||||
|
var code = '';
|
||||||
|
async.eachSeries(scripts, function (srcPath, next) {
|
||||||
|
fs.readFile(path.join(__dirname, '..', srcPath), function (err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
code += buffer.toString();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var minified = uglify.minify(code, {
|
||||||
|
compress: false,
|
||||||
|
});
|
||||||
|
if (!minified.code) {
|
||||||
|
return callback(new Error('[[error:failed-to-minify]]'));
|
||||||
|
}
|
||||||
|
fs.writeFile(path.join(__dirname, '../public/installer.min.js'), minified.code, callback);
|
||||||
|
} catch (e) {
|
||||||
|
callback(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = web;
|
||||||
|
|||||||
204
loader.js
@@ -1,61 +1,73 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
const fs = require('fs');
|
var fs = require('fs');
|
||||||
const url = require('url');
|
var url = require('url');
|
||||||
const path = require('path');
|
var path = require('path');
|
||||||
const { fork } = require('child_process');
|
var fork = require('child_process').fork;
|
||||||
const logrotate = require('logrotate-stream');
|
var async = require('async');
|
||||||
const { mkdirp } = require('mkdirp');
|
var logrotate = require('logrotate-stream');
|
||||||
|
|
||||||
const file = require('./src/file');
|
var file = require('./src/file');
|
||||||
const pkg = require('./package.json');
|
var pkg = require('./package.json');
|
||||||
|
|
||||||
const pathToConfig = path.resolve(__dirname, process.env.CONFIG || 'config.json');
|
|
||||||
|
|
||||||
nconf.argv().env().file({
|
nconf.argv().env().file({
|
||||||
file: pathToConfig,
|
file: path.join(__dirname, 'config.json'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const pidFilePath = path.join(__dirname, 'pidfile');
|
var pidFilePath = path.join(__dirname, 'pidfile');
|
||||||
|
var outputLogFilePath = path.join(__dirname, 'logs/output.log');
|
||||||
|
var output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true });
|
||||||
|
var silent = nconf.get('silent') === 'false' ? false : nconf.get('silent') !== false;
|
||||||
|
var numProcs;
|
||||||
|
var workers = [];
|
||||||
|
var Loader = {
|
||||||
|
timesStarted: 0,
|
||||||
|
};
|
||||||
|
var appPath = path.join(__dirname, 'app.js');
|
||||||
|
|
||||||
const outputLogFilePath = path.join(__dirname, nconf.get('logFile') || 'logs/output.log');
|
Loader.init = function (callback) {
|
||||||
|
|
||||||
const logDir = path.dirname(outputLogFilePath);
|
|
||||||
if (!fs.existsSync(logDir)) {
|
|
||||||
mkdirp.sync(path.dirname(outputLogFilePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = logrotate({ file: outputLogFilePath, size: '10m', keep: 3, compress: true });
|
|
||||||
const silent = nconf.get('silent') === 'false' ? false : nconf.get('silent') !== false;
|
|
||||||
let numProcs;
|
|
||||||
const workers = [];
|
|
||||||
const Loader = {};
|
|
||||||
const appPath = path.join(__dirname, 'app.js');
|
|
||||||
|
|
||||||
Loader.init = function () {
|
|
||||||
if (silent) {
|
if (silent) {
|
||||||
console.log = (...args) => {
|
console.log = function () {
|
||||||
output.write(`${args.join(' ')}\n`);
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
output.write(args.join(' ') + '\n');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('SIGHUP', Loader.restart);
|
process.on('SIGHUP', Loader.restart);
|
||||||
|
process.on('SIGUSR2', Loader.reload);
|
||||||
process.on('SIGTERM', Loader.stop);
|
process.on('SIGTERM', Loader.stop);
|
||||||
|
callback();
|
||||||
};
|
};
|
||||||
|
|
||||||
Loader.displayStartupMessages = function () {
|
Loader.displayStartupMessages = function (callback) {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(`NodeBB v${pkg.version} Copyright (C) 2013-${(new Date()).getFullYear()} NodeBB Inc.`);
|
console.log('NodeBB v' + pkg.version + ' Copyright (C) 2013-2014 NodeBB Inc.');
|
||||||
console.log('This program comes with ABSOLUTELY NO WARRANTY.');
|
console.log('This program comes with ABSOLUTELY NO WARRANTY.');
|
||||||
console.log('This is free software, and you are welcome to redistribute it under certain conditions.');
|
console.log('This is free software, and you are welcome to redistribute it under certain conditions.');
|
||||||
console.log('For the full license, please visit: http://www.gnu.org/copyleft/gpl.html');
|
console.log('For the full license, please visit: http://www.gnu.org/copyleft/gpl.html');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
callback();
|
||||||
};
|
};
|
||||||
|
|
||||||
Loader.addWorkerEvents = function (worker) {
|
Loader.addWorkerEvents = function (worker) {
|
||||||
worker.on('exit', (code, signal) => {
|
worker.on('exit', function (code, signal) {
|
||||||
console.log(`[cluster] Child Process (${worker.pid}) has exited (code: ${code}, signal: ${signal})`);
|
if (code !== 0) {
|
||||||
|
if (Loader.timesStarted < numProcs * 3) {
|
||||||
|
Loader.timesStarted += 1;
|
||||||
|
if (Loader.crashTimer) {
|
||||||
|
clearTimeout(Loader.crashTimer);
|
||||||
|
}
|
||||||
|
Loader.crashTimer = setTimeout(function () {
|
||||||
|
Loader.timesStarted = 0;
|
||||||
|
}, 10000);
|
||||||
|
} else {
|
||||||
|
console.log((numProcs * 3) + ' restarts in 10 seconds, most likely an error on startup. Halting.');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[cluster] Child Process (' + worker.pid + ') has exited (code: ' + code + ', signal: ' + signal + ')');
|
||||||
if (!(worker.suicide || code === 0)) {
|
if (!(worker.suicide || code === 0)) {
|
||||||
console.log('[cluster] Spinning up another process...');
|
console.log('[cluster] Spinning up another process...');
|
||||||
|
|
||||||
@@ -63,54 +75,48 @@ Loader.addWorkerEvents = function (worker) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
worker.on('message', (message) => {
|
worker.on('message', function (message) {
|
||||||
if (message && typeof message === 'object' && message.action) {
|
if (message && typeof message === 'object' && message.action) {
|
||||||
switch (message.action) {
|
switch (message.action) {
|
||||||
case 'restart':
|
case 'restart':
|
||||||
console.log('[cluster] Restarting...');
|
console.log('[cluster] Restarting...');
|
||||||
Loader.restart();
|
Loader.restart();
|
||||||
break;
|
break;
|
||||||
case 'pubsub':
|
case 'reload':
|
||||||
workers.forEach((w) => {
|
console.log('[cluster] Reloading...');
|
||||||
w.send(message);
|
Loader.reload();
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'socket.io':
|
|
||||||
workers.forEach((w) => {
|
|
||||||
if (w !== worker) {
|
|
||||||
w.send(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Loader.start = function () {
|
Loader.start = function (callback) {
|
||||||
numProcs = getPorts().length;
|
numProcs = getPorts().length;
|
||||||
console.log(`Clustering enabled: Spinning up ${numProcs} process(es).\n`);
|
console.log('Clustering enabled: Spinning up ' + numProcs + ' process(es).\n');
|
||||||
|
|
||||||
for (let x = 0; x < numProcs; x += 1) {
|
for (var x = 0; x < numProcs; x += 1) {
|
||||||
forkWorker(x, x === 0);
|
forkWorker(x, x === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function forkWorker(index, isPrimary) {
|
function forkWorker(index, isPrimary) {
|
||||||
const ports = getPorts();
|
var ports = getPorts();
|
||||||
const args = [];
|
var args = [];
|
||||||
if (nconf.get('max-memory')) {
|
|
||||||
args.push(`--max-old-space-size=${nconf.get('max-memory')}`);
|
|
||||||
}
|
|
||||||
if (!ports[index]) {
|
if (!ports[index]) {
|
||||||
return console.log(`[cluster] invalid port for worker : ${index} ports: ${ports.length}`);
|
return console.log('[cluster] invalid port for worker : ' + index + ' ports: ' + ports.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.isPrimary = isPrimary;
|
process.env.isPrimary = isPrimary;
|
||||||
process.env.isCluster = nconf.get('isCluster') || ports.length > 1;
|
process.env.isCluster = ports.length > 1;
|
||||||
process.env.port = ports[index];
|
process.env.port = ports[index];
|
||||||
|
|
||||||
const worker = fork(appPath, args, {
|
var worker = fork(appPath, args, {
|
||||||
silent: silent,
|
silent: silent,
|
||||||
env: process.env,
|
env: process.env,
|
||||||
});
|
});
|
||||||
@@ -123,20 +129,20 @@ function forkWorker(index, isPrimary) {
|
|||||||
Loader.addWorkerEvents(worker);
|
Loader.addWorkerEvents(worker);
|
||||||
|
|
||||||
if (silent) {
|
if (silent) {
|
||||||
const output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true });
|
var output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true });
|
||||||
worker.stdout.pipe(output);
|
worker.stdout.pipe(output);
|
||||||
worker.stderr.pipe(output);
|
worker.stderr.pipe(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPorts() {
|
function getPorts() {
|
||||||
const _url = nconf.get('url');
|
var _url = nconf.get('url');
|
||||||
if (!_url) {
|
if (!_url) {
|
||||||
console.log('[cluster] url is undefined, please check your config.json');
|
console.log('[cluster] url is undefined, please check your config.json');
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
const urlObject = url.parse(_url);
|
var urlObject = url.parse(_url);
|
||||||
let port = nconf.get('PORT') || nconf.get('port') || urlObject.port || 4567;
|
var port = nconf.get('port') || urlObject.port || 4567;
|
||||||
if (!Array.isArray(port)) {
|
if (!Array.isArray(port)) {
|
||||||
port = [port];
|
port = [port];
|
||||||
}
|
}
|
||||||
@@ -146,16 +152,17 @@ function getPorts() {
|
|||||||
Loader.restart = function () {
|
Loader.restart = function () {
|
||||||
killWorkers();
|
killWorkers();
|
||||||
|
|
||||||
|
var pathToConfig = path.join(__dirname, '/config.json');
|
||||||
nconf.remove('file');
|
nconf.remove('file');
|
||||||
nconf.use('file', { file: pathToConfig });
|
nconf.use('file', { file: pathToConfig });
|
||||||
|
|
||||||
fs.readFile(pathToConfig, { encoding: 'utf-8' }, (err, configFile) => {
|
fs.readFile(pathToConfig, { encoding: 'utf-8' }, function (err, configFile) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error reading config');
|
console.log('Error reading config : ' + err.message);
|
||||||
throw err;
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const conf = JSON.parse(configFile);
|
var conf = JSON.parse(configFile);
|
||||||
|
|
||||||
nconf.stores.env.readOnly = false;
|
nconf.stores.env.readOnly = false;
|
||||||
nconf.set('url', conf.url);
|
nconf.set('url', conf.url);
|
||||||
@@ -168,6 +175,14 @@ Loader.restart = function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loader.reload = function () {
|
||||||
|
workers.forEach(function (worker) {
|
||||||
|
worker.send({
|
||||||
|
action: 'reload',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
Loader.stop = function () {
|
Loader.stop = function () {
|
||||||
killWorkers();
|
killWorkers();
|
||||||
|
|
||||||
@@ -178,41 +193,36 @@ Loader.stop = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function killWorkers() {
|
function killWorkers() {
|
||||||
workers.forEach((worker) => {
|
workers.forEach(function (worker) {
|
||||||
worker.suicide = true;
|
worker.suicide = true;
|
||||||
worker.kill();
|
worker.kill();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.open(pathToConfig, 'r', (err) => {
|
Loader.notifyWorkers = function (msg, worker_pid) {
|
||||||
if (err) {
|
worker_pid = parseInt(worker_pid, 10);
|
||||||
// No config detected, kickstart web installer
|
workers.forEach(function (worker) {
|
||||||
fork('app');
|
if (parseInt(worker.pid, 10) !== worker_pid) {
|
||||||
return;
|
try {
|
||||||
|
worker.send(msg);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[cluster/notifyWorkers] Failed to reach pid ' + worker_pid);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.open(path.join(__dirname, 'config.json'), 'r', function (err) {
|
||||||
|
if (!err) {
|
||||||
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
|
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
|
||||||
if (file.existsSync(pidFilePath)) {
|
if (file.existsSync(pidFilePath)) {
|
||||||
let pid = 0;
|
|
||||||
try {
|
try {
|
||||||
pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
|
var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
|
||||||
if (pid) {
|
|
||||||
process.kill(pid, 0);
|
process.kill(pid, 0);
|
||||||
console.info(`Process "${pid}" from pidfile already running, exiting`);
|
|
||||||
process.exit();
|
process.exit();
|
||||||
} else {
|
} catch (e) {
|
||||||
console.info(`Invalid pid "${pid}" from pidfile, deleting pidfile`);
|
|
||||||
fs.unlinkSync(pidFilePath);
|
fs.unlinkSync(pidFilePath);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'ESRCH') {
|
|
||||||
console.info(`Process "${pid}" from pidfile not found, deleting pidfile`);
|
|
||||||
fs.unlinkSync(pidFilePath);
|
|
||||||
} else {
|
|
||||||
console.error(err.stack);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
require('daemon')({
|
require('daemon')({
|
||||||
@@ -221,14 +231,20 @@ fs.open(pathToConfig, 'r', (err) => {
|
|||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(pidFilePath, String(process.pid));
|
fs.writeFileSync(pidFilePath, process.pid);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
Loader.init();
|
async.series([
|
||||||
Loader.displayStartupMessages();
|
Loader.init,
|
||||||
Loader.start();
|
Loader.displayStartupMessages,
|
||||||
} catch (err) {
|
Loader.start,
|
||||||
console.error('[loader] Error during startup');
|
], function (err) {
|
||||||
throw err;
|
if (err) {
|
||||||
|
console.log('[loader] Error during startup: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// No config detected, kickstart web installer
|
||||||
|
require('child_process').fork('app');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
536
nodebb
@@ -2,4 +2,538 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('./src/cli');
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var cproc = require('child_process');
|
||||||
|
|
||||||
|
// check to make sure dependencies are installed
|
||||||
|
try {
|
||||||
|
fs.readFileSync(path.join(__dirname, 'node_modules/async/package.json'));
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'ENOENT') {
|
||||||
|
process.stdout.write('Dependencies not yet installed.\n');
|
||||||
|
process.stdout.write('Installing them now...\n\n');
|
||||||
|
|
||||||
|
cproc.execSync('npm i --production', {
|
||||||
|
cwd: __dirname,
|
||||||
|
stdio: [0, 1, 2],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var minimist;
|
||||||
|
var request;
|
||||||
|
var semver;
|
||||||
|
var prompt;
|
||||||
|
var async;
|
||||||
|
|
||||||
|
try {
|
||||||
|
require('colors');
|
||||||
|
minimist = require('minimist');
|
||||||
|
request = require('request');
|
||||||
|
semver = require('semver');
|
||||||
|
prompt = require('prompt');
|
||||||
|
async = require('async');
|
||||||
|
} catch (e) {
|
||||||
|
process.stdout.write(
|
||||||
|
'\x1b[31mNodeBB could not be initialised because there was an error while loading dependencies.\n' +
|
||||||
|
'Please run "\x1b[33mnpm install --production\x1b[31m" and try again.\x1b[0m\n\n' +
|
||||||
|
'For more information, please see: https://docs.nodebb.org/installing/os/\n\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = minimist(process.argv.slice(2));
|
||||||
|
|
||||||
|
var loaderPath = path.join(__dirname, 'loader.js');
|
||||||
|
var appPath = path.join(__dirname, 'app.js');
|
||||||
|
|
||||||
|
if (args.dev) {
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRunningPid(callback) {
|
||||||
|
fs.readFile(path.join(__dirname, 'pidfile'), {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
}, function (err, pid) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
process.kill(parseInt(pid, 10), 0);
|
||||||
|
callback(null, parseInt(pid, 10));
|
||||||
|
} catch (e) {
|
||||||
|
callback(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getCurrentVersion(callback) {
|
||||||
|
fs.readFile(path.join(__dirname, 'package.json'), { encoding: 'utf-8' }, function (err, pkg) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pkg = JSON.parse(pkg);
|
||||||
|
return callback(null, pkg.version);
|
||||||
|
} catch (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function fork(args) {
|
||||||
|
return cproc.fork(appPath, args, {
|
||||||
|
cwd: __dirname,
|
||||||
|
silent: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getInstalledPlugins(callback) {
|
||||||
|
async.parallel({
|
||||||
|
files: async.apply(fs.readdir, path.join(__dirname, 'node_modules')),
|
||||||
|
deps: async.apply(fs.readFile, path.join(__dirname, 'package.json'), { encoding: 'utf-8' }),
|
||||||
|
}, function (err, payload) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/;
|
||||||
|
var moduleName;
|
||||||
|
var isGitRepo;
|
||||||
|
|
||||||
|
payload.files = payload.files.filter(function (file) {
|
||||||
|
return isNbbModule.test(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
payload.deps = JSON.parse(payload.deps).dependencies;
|
||||||
|
payload.bundled = [];
|
||||||
|
payload.installed = [];
|
||||||
|
} catch (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (moduleName in payload.deps) {
|
||||||
|
if (isNbbModule.test(moduleName)) {
|
||||||
|
payload.bundled.push(moduleName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whittle down deps to send back only extraneously installed plugins/themes/etc
|
||||||
|
payload.files.forEach(function (moduleName) {
|
||||||
|
try {
|
||||||
|
fs.accessSync(path.join(__dirname, 'node_modules/' + moduleName, '.git'));
|
||||||
|
isGitRepo = true;
|
||||||
|
} catch (e) {
|
||||||
|
isGitRepo = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
payload.files.indexOf(moduleName) !== -1 && // found in `node_modules/`
|
||||||
|
payload.bundled.indexOf(moduleName) === -1 && // not found in `package.json`
|
||||||
|
!fs.lstatSync(path.join(__dirname, 'node_modules/' + moduleName)).isSymbolicLink() && // is not a symlink
|
||||||
|
!isGitRepo // .git/ does not exist, so it is not a git repository
|
||||||
|
) {
|
||||||
|
payload.installed.push(moduleName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getModuleVersions(payload.installed, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getModuleVersions(modules, callback) {
|
||||||
|
var versionHash = {};
|
||||||
|
|
||||||
|
async.eachLimit(modules, 50, function (module, next) {
|
||||||
|
fs.readFile(path.join(__dirname, 'node_modules/' + module + '/package.json'), { encoding: 'utf-8' }, function (err, pkg) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pkg = JSON.parse(pkg);
|
||||||
|
versionHash[module] = pkg.version;
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
callback(err, versionHash);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function checkPlugins(standalone, callback) {
|
||||||
|
if (standalone) {
|
||||||
|
process.stdout.write('Checking installed plugins and themes for updates... ');
|
||||||
|
}
|
||||||
|
|
||||||
|
async.waterfall([
|
||||||
|
async.apply(async.parallel, {
|
||||||
|
plugins: async.apply(getInstalledPlugins),
|
||||||
|
version: async.apply(getCurrentVersion),
|
||||||
|
}),
|
||||||
|
function (payload, next) {
|
||||||
|
var toCheck = Object.keys(payload.plugins);
|
||||||
|
|
||||||
|
if (!toCheck.length) {
|
||||||
|
process.stdout.write('OK'.green + '\n'.reset);
|
||||||
|
return next(null, []); // no extraneous plugins installed
|
||||||
|
}
|
||||||
|
|
||||||
|
request({
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://packages.nodebb.org/api/v1/suggest?version=' + payload.version + '&package[]=' + toCheck.join('&package[]='),
|
||||||
|
json: true,
|
||||||
|
}, function (err, res, body) {
|
||||||
|
if (err) {
|
||||||
|
process.stdout.write('error'.red + '\n'.reset);
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
process.stdout.write('OK'.green + '\n'.reset);
|
||||||
|
|
||||||
|
if (!Array.isArray(body) && toCheck.length === 1) {
|
||||||
|
body = [body];
|
||||||
|
}
|
||||||
|
|
||||||
|
var current;
|
||||||
|
var suggested;
|
||||||
|
var upgradable = body.map(function (suggestObj) {
|
||||||
|
current = payload.plugins[suggestObj.package];
|
||||||
|
suggested = suggestObj.version;
|
||||||
|
|
||||||
|
if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) {
|
||||||
|
return {
|
||||||
|
name: suggestObj.package,
|
||||||
|
current: current,
|
||||||
|
suggested: suggested,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
next(null, upgradable);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
], callback);
|
||||||
|
}
|
||||||
|
function upgradePlugins(callback) {
|
||||||
|
var standalone = false;
|
||||||
|
if (typeof callback !== 'function') {
|
||||||
|
callback = function () {};
|
||||||
|
standalone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPlugins(standalone, function (err, found) {
|
||||||
|
if (err) {
|
||||||
|
process.stdout.write('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found && found.length) {
|
||||||
|
process.stdout.write('\nA total of ' + String(found.length).bold + ' package(s) can be upgraded:\n');
|
||||||
|
found.forEach(function (suggestObj) {
|
||||||
|
process.stdout.write(' * '.yellow + suggestObj.name.reset + ' (' + suggestObj.current.yellow + ' -> '.reset + suggestObj.suggested.green + ')\n'.reset);
|
||||||
|
});
|
||||||
|
process.stdout.write('\n');
|
||||||
|
} else {
|
||||||
|
if (standalone) {
|
||||||
|
process.stdout.write('\nAll packages up-to-date!'.green + '\n'.reset);
|
||||||
|
}
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt.message = '';
|
||||||
|
prompt.delimiter = '';
|
||||||
|
|
||||||
|
prompt.start();
|
||||||
|
prompt.get({
|
||||||
|
name: 'upgrade',
|
||||||
|
description: 'Proceed with upgrade (y|n)?'.reset,
|
||||||
|
type: 'string',
|
||||||
|
}, function (err, result) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['y', 'Y', 'yes', 'YES'].indexOf(result.upgrade) !== -1) {
|
||||||
|
process.stdout.write('\nUpgrading packages...');
|
||||||
|
var args = ['i'];
|
||||||
|
found.forEach(function (suggestObj) {
|
||||||
|
args.push(suggestObj.name + '@' + suggestObj.suggested);
|
||||||
|
});
|
||||||
|
|
||||||
|
cproc.execFile((process.platform === 'win32') ? 'npm.cmd' : 'npm', args, { stdio: 'ignore' }, function (err) {
|
||||||
|
if (!err) {
|
||||||
|
process.stdout.write(' OK\n'.green);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
process.stdout.write('\nPackage upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade-plugins'.green + '".\n'.reset);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var commands = {
|
||||||
|
status: {
|
||||||
|
description: 'View the status of the NodeBB server',
|
||||||
|
usage: 'Usage: ' + './nodebb status'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
getRunningPid(function (err, pid) {
|
||||||
|
if (!err) {
|
||||||
|
process.stdout.write('\nNodeBB Running '.bold + '(pid '.cyan + pid.toString().cyan + ')\n'.cyan);
|
||||||
|
process.stdout.write('\t"' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
|
||||||
|
process.stdout.write('\t"' + './nodebb log'.yellow + '" to view server output\n');
|
||||||
|
process.stdout.write('\t"' + './nodebb restart'.yellow + '" to restart NodeBB\n\n');
|
||||||
|
} else {
|
||||||
|
process.stdout.write('\nNodeBB is not running\n'.bold);
|
||||||
|
process.stdout.write('\t"' + './nodebb start'.yellow + '" to launch the NodeBB server\n\n'.reset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
description: 'Start the NodeBB server',
|
||||||
|
usage: 'Usage: ' + './nodebb start'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
process.stdout.write('\nStarting NodeBB\n'.bold);
|
||||||
|
process.stdout.write(' "' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
|
||||||
|
process.stdout.write(' "' + './nodebb log'.yellow + '" to view server output\n');
|
||||||
|
process.stdout.write(' "' + './nodebb restart'.yellow + '" to restart NodeBB\n\n'.reset);
|
||||||
|
|
||||||
|
// Spawn a new NodeBB process
|
||||||
|
cproc.fork(loaderPath, process.argv.slice(3), {
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stop: {
|
||||||
|
description: 'Stop the NodeBB server',
|
||||||
|
usage: 'Usage: ' + './nodebb stop'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
getRunningPid(function (err, pid) {
|
||||||
|
if (!err) {
|
||||||
|
process.kill(pid, 'SIGTERM');
|
||||||
|
process.stdout.write('Stopping NodeBB. Goodbye!\n');
|
||||||
|
} else {
|
||||||
|
process.stdout.write('NodeBB is already stopped.\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
restart: {
|
||||||
|
description: 'Restart the NodeBB server',
|
||||||
|
usage: 'Usage: ' + './nodebb restart'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
getRunningPid(function (err, pid) {
|
||||||
|
if (!err) {
|
||||||
|
process.kill(pid, 'SIGHUP');
|
||||||
|
process.stdout.write('\nRestarting NodeBB\n'.bold);
|
||||||
|
} else {
|
||||||
|
process.stdout.write('NodeBB could not be restarted, as a running instance could not be found.\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
log: {
|
||||||
|
description: 'Open the output log (useful for debugging)',
|
||||||
|
usage: 'Usage: ' + './nodebb log'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
process.stdout.write('\nHit '.red + 'Ctrl-C '.bold + 'to exit'.red);
|
||||||
|
process.stdout.write('\n\n'.reset);
|
||||||
|
cproc.spawn('tail', ['-F', './logs/output.log'], {
|
||||||
|
cwd: __dirname,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
slog: {
|
||||||
|
description: 'Start the NodeBB server and view the live output log',
|
||||||
|
usage: 'Usage: ' + './nodebb slog'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
process.stdout.write('\nStarting NodeBB with logging output\n'.bold);
|
||||||
|
process.stdout.write('\nHit '.red + 'Ctrl-C '.bold + 'to exit'.red);
|
||||||
|
process.stdout.write('\n\n'.reset);
|
||||||
|
|
||||||
|
// Spawn a new NodeBB process
|
||||||
|
cproc.fork(loaderPath, {
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
cproc.spawn('tail', ['-F', './logs/output.log'], {
|
||||||
|
cwd: __dirname,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dev: {
|
||||||
|
description: 'Start NodeBB in verbose development mode',
|
||||||
|
usage: 'Usage: ' + './nodebb dev'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
cproc.fork(loaderPath, ['--no-daemon', '--no-silent'], {
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
description: 'Compile static assets (CSS, Javascript, etc)',
|
||||||
|
usage: 'Usage: ' + './nodebb build'.yellow + ' [js,clientCSS,acpCSS,tpl,lang]'.red + '\n' +
|
||||||
|
' e.g. ' + './nodebb build js,tpl'.yellow + '\tbuilds JS and templates\n' +
|
||||||
|
' ' + './nodebb build'.yellow + '\t\tbuilds all targets\n',
|
||||||
|
handler: function () {
|
||||||
|
var arr = ['--build'].concat(process.argv.slice(3));
|
||||||
|
fork(arr);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup: {
|
||||||
|
description: 'Run the NodeBB setup script',
|
||||||
|
usage: 'Usage: ' + './nodebb setup'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
var arr = ['--setup'].concat(process.argv.slice(3));
|
||||||
|
fork(arr);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reset: {
|
||||||
|
description: 'Disable plugins and restore the default theme',
|
||||||
|
usage: 'Usage: ' + './nodebb reset '.yellow + '{-t|-p|-w|-s|-a}'.red + '\n' +
|
||||||
|
' -t <theme>\tuse specified theme\n' +
|
||||||
|
' -p <plugin>\tdisable specified plugin\n' +
|
||||||
|
'\n' +
|
||||||
|
' -t\t\tuse default theme\n' +
|
||||||
|
' -p\t\tdisable all but core plugins\n' +
|
||||||
|
' -w\t\twidgets\n' +
|
||||||
|
' -s\t\tsettings\n' +
|
||||||
|
' -a\t\tall of the above\n',
|
||||||
|
handler: function () {
|
||||||
|
var arr = ['--reset'].concat(process.argv.slice(3));
|
||||||
|
fork(arr);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
activate: {
|
||||||
|
description: 'Activate a plugin for the next startup of NodeBB',
|
||||||
|
usage: 'Usage: ' + './nodebb activate <plugin>'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
var name = args._[1];
|
||||||
|
if (!name) {
|
||||||
|
process.stdout.write(commands.activate.usage + '\n');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
if (name.startsWith('nodebb-theme')) {
|
||||||
|
fork(['--reset', '-t', name]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var arr = ['--activate=' + name].concat(process.argv.slice(4));
|
||||||
|
fork(arr);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
description: 'List all installed plugins',
|
||||||
|
usage: 'Usage: ' + './nodebb plugins'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
var arr = ['--plugins'].concat(process.argv.slice(3));
|
||||||
|
fork(arr);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
upgrade: {
|
||||||
|
description: 'Run NodeBB upgrade scripts, ensure packages are up-to-date',
|
||||||
|
usage: 'Usage: ' + './nodebb upgrade'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
if (process.argv[3]) {
|
||||||
|
process.stdout.write('\nUpdating NodeBB data store schema...\n'.yellow);
|
||||||
|
var arr = ['--upgrade'].concat(process.argv.slice(3));
|
||||||
|
var upgradeProc = fork(arr);
|
||||||
|
|
||||||
|
return upgradeProc.on('close', function (err) {
|
||||||
|
if (err) {
|
||||||
|
process.stdout.write('\nError'.red + ': ' + err.message + '\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
function (next) {
|
||||||
|
process.stdout.write('1. '.bold + 'Bringing base dependencies up to date... '.yellow);
|
||||||
|
cproc.exec('npm i --production', { cwd: __dirname, stdio: 'ignore' }, next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
process.stdout.write('OK\n'.green);
|
||||||
|
process.stdout.write('2. '.bold + 'Checking installed plugins for updates... '.yellow);
|
||||||
|
upgradePlugins(next);
|
||||||
|
},
|
||||||
|
function (next) {
|
||||||
|
process.stdout.write('3. '.bold + 'Updating NodeBB data store schema...\n'.yellow);
|
||||||
|
var arr = ['--upgrade'].concat(process.argv.slice(3));
|
||||||
|
var upgradeProc = fork(arr);
|
||||||
|
|
||||||
|
upgradeProc.on('close', next);
|
||||||
|
},
|
||||||
|
], function (err) {
|
||||||
|
if (err) {
|
||||||
|
process.stdout.write('\nError'.red + ': ' + err.message + '\n');
|
||||||
|
} else {
|
||||||
|
var message = 'NodeBB Upgrade Complete!';
|
||||||
|
// some consoles will return undefined/zero columns, so just use 2 spaces in upgrade script if we can't get our column count
|
||||||
|
var columns = process.stdout.columns;
|
||||||
|
var spaces = columns ? new Array(Math.floor(columns / 2) - (message.length / 2) + 1).join(' ') : ' ';
|
||||||
|
|
||||||
|
process.stdout.write('OK\n'.green);
|
||||||
|
process.stdout.write('\n' + spaces + message.green.bold + '\n\n'.reset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
upgradePlugins: {
|
||||||
|
hidden: true,
|
||||||
|
description: '',
|
||||||
|
handler: function () {
|
||||||
|
upgradePlugins();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
description: 'Outputs the last ten (10) administrative events recorded by NodeBB',
|
||||||
|
usage: 'Usage: ' + './nodebb events'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
fork(['--events']);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
help: {
|
||||||
|
description: 'Display the help message for a given command',
|
||||||
|
usage: 'Usage: ' + './nodebb help <command>'.yellow,
|
||||||
|
handler: function () {
|
||||||
|
var command = commands[args._[1]];
|
||||||
|
if (command) {
|
||||||
|
process.stdout.write(command.description + '\n'.reset);
|
||||||
|
process.stdout.write(command.usage + '\n'.reset);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var keys = Object.keys(commands).filter(function (key) {
|
||||||
|
return !commands[key].hidden;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdout.write('\nWelcome to NodeBB\n\n'.bold);
|
||||||
|
process.stdout.write('Usage: ./nodebb {' + keys.join('|') + '}\n\n');
|
||||||
|
|
||||||
|
var usage = keys.map(function (key) {
|
||||||
|
var line = '\t' + key.yellow + (key.length < 8 ? '\t\t' : '\t');
|
||||||
|
return line + commands[key].description;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
process.stdout.write(usage + '\n'.reset);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
commands['upgrade-plugins'] = commands.upgradePlugins;
|
||||||
|
|
||||||
|
if (!commands[args._[0]]) {
|
||||||
|
commands.help.handler();
|
||||||
|
} else {
|
||||||
|
commands[args._[0]].handler();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
@echo off && cd %~dp0 && node ./nodebb %*
|
node ./nodebb %*
|
||||||
|
|||||||
139
package.json
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
{
|
||||||
|
"name": "nodebb",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"description": "NodeBB Forum",
|
||||||
|
"version": "1.6.1",
|
||||||
|
"homepage": "http://www.nodebb.org",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/NodeBB/NodeBB/"
|
||||||
|
},
|
||||||
|
"main": "app.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node loader.js",
|
||||||
|
"lint": "eslint --cache ./nodebb .",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"test": "istanbul cover node_modules/mocha/bin/_mocha -- -R dot",
|
||||||
|
"coveralls": "istanbul cover _mocha --report lcovonly -- -R dot && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ace-builds": "^1.2.8",
|
||||||
|
"async": "2.5.0",
|
||||||
|
"autoprefixer": "7.1.4",
|
||||||
|
"bcryptjs": "2.4.3",
|
||||||
|
"benchpressjs": "^1.1.0",
|
||||||
|
"body-parser": "^1.18.2",
|
||||||
|
"bootstrap": "^3.3.7",
|
||||||
|
"chart.js": "^2.6.0",
|
||||||
|
"colors": "^1.1.2",
|
||||||
|
"compression": "^1.7.1",
|
||||||
|
"connect-ensure-login": "^0.1.1",
|
||||||
|
"connect-flash": "^0.1.1",
|
||||||
|
"connect-mongo": "1.3.2",
|
||||||
|
"connect-multiparty": "^2.0.0",
|
||||||
|
"connect-redis": "3.3.2",
|
||||||
|
"cookie-parser": "^1.4.3",
|
||||||
|
"cron": "^1.2.1",
|
||||||
|
"cropperjs": "^1.0.0",
|
||||||
|
"csurf": "^1.9.0",
|
||||||
|
"daemon": "^1.1.0",
|
||||||
|
"express": "^4.16.1",
|
||||||
|
"express-session": "^1.15.6",
|
||||||
|
"express-useragent": "1.0.8",
|
||||||
|
"html-to-text": "3.3.0",
|
||||||
|
"ipaddr.js": "^1.5.2",
|
||||||
|
"jimp": "0.2.28",
|
||||||
|
"jquery": "^3.2.1",
|
||||||
|
"json-2-csv": "^2.1.1",
|
||||||
|
"less": "^2.7.2",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"logrotate-stream": "^0.2.5",
|
||||||
|
"lru-cache": "4.1.1",
|
||||||
|
"mime": "^2.0.3",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"mongodb": "2.2.31",
|
||||||
|
"morgan": "^1.9.0",
|
||||||
|
"mousetrap": "^1.6.1",
|
||||||
|
"nconf": "^0.8.5",
|
||||||
|
"nodebb-plugin-composer-default": "6.0.1",
|
||||||
|
"nodebb-plugin-dbsearch": "2.0.6",
|
||||||
|
"nodebb-plugin-emoji-extended": "1.1.1",
|
||||||
|
"nodebb-plugin-emoji-one": "1.2.1",
|
||||||
|
"nodebb-plugin-markdown": "8.1.0",
|
||||||
|
"nodebb-plugin-mentions": "2.1.6",
|
||||||
|
"nodebb-plugin-soundpack-default": "1.0.0",
|
||||||
|
"nodebb-plugin-spam-be-gone": "0.5.1",
|
||||||
|
"nodebb-rewards-essentials": "0.0.9",
|
||||||
|
"nodebb-theme-lavender": "4.1.1",
|
||||||
|
"nodebb-theme-persona": "6.1.2",
|
||||||
|
"nodebb-theme-slick": "1.1.1",
|
||||||
|
"nodebb-theme-vanilla": "7.1.1",
|
||||||
|
"nodebb-widget-essentials": "3.0.6",
|
||||||
|
"nodemailer": "4.1.1",
|
||||||
|
"passport": "^0.4.0",
|
||||||
|
"passport-local": "1.0.0",
|
||||||
|
"postcss": "6.0.12",
|
||||||
|
"postcss-clean": "1.1.0",
|
||||||
|
"promise-polyfill": "^6.0.2",
|
||||||
|
"prompt": "^1.0.0",
|
||||||
|
"redis": "2.8.0",
|
||||||
|
"request": "2.83.0",
|
||||||
|
"rimraf": "2.6.2",
|
||||||
|
"rss": "^1.2.2",
|
||||||
|
"sanitize-html": "^1.14.1",
|
||||||
|
"semver": "^5.4.1",
|
||||||
|
"serve-favicon": "^2.4.5",
|
||||||
|
"sitemap": "^1.13.0",
|
||||||
|
"socket.io": "2.0.3",
|
||||||
|
"socket.io-client": "2.0.3",
|
||||||
|
"socket.io-redis": "5.2.0",
|
||||||
|
"socketio-wildcard": "2.0.0",
|
||||||
|
"spdx-license-list": "^3.0.1",
|
||||||
|
"string": "^3.3.3",
|
||||||
|
"toobusy-js": "^0.5.1",
|
||||||
|
"uglify-js": "^3.1.3",
|
||||||
|
"validator": "9.0.0",
|
||||||
|
"winston": "^2.3.1",
|
||||||
|
"xml": "^1.0.1",
|
||||||
|
"xregexp": "3.2.0",
|
||||||
|
"zxcvbn": "^4.4.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"coveralls": "^3.0.0",
|
||||||
|
"eslint": "^4.8.0",
|
||||||
|
"eslint-config-airbnb-base": "^12.0.1",
|
||||||
|
"eslint-plugin-import": "^2.7.0",
|
||||||
|
"grunt": "^1.0.1",
|
||||||
|
"grunt-contrib-watch": "^1.0.0",
|
||||||
|
"istanbul": "^0.4.5",
|
||||||
|
"jsdom": "^11.3.0",
|
||||||
|
"mocha": "^3.5.3",
|
||||||
|
"mocha-lcov-reporter": "^1.3.0",
|
||||||
|
"xmlhttprequest": "1.8.0",
|
||||||
|
"xmlhttprequest-ssl": "1.5.3"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/NodeBB/NodeBB/issues"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"maintainers": [
|
||||||
|
{
|
||||||
|
"name": "Andrew Rodrigues",
|
||||||
|
"email": "andrew@nodebb.org",
|
||||||
|
"url": "https://github.com/psychobunny"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Julian Lam",
|
||||||
|
"email": "julian@nodebb.org",
|
||||||
|
"url": "https://github.com/julianlam"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Barış Soner Uşaklı",
|
||||||
|
"email": "baris@nodebb.org",
|
||||||
|
"url": "https://github.com/barisusakli"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,3 +1,57 @@
|
|||||||
{
|
{
|
||||||
"extends": "nodebb/public"
|
"globals": {
|
||||||
|
"app": true,
|
||||||
|
"io": true,
|
||||||
|
"socket": true,
|
||||||
|
"ajaxify": true,
|
||||||
|
"config": true,
|
||||||
|
"RELATIVE_PATH": true,
|
||||||
|
"utils": true,
|
||||||
|
"overrides": true,
|
||||||
|
"componentHandler": true,
|
||||||
|
"bootbox": true,
|
||||||
|
"Visibility": true,
|
||||||
|
"Tinycon": true,
|
||||||
|
"Promise": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"jquery": true,
|
||||||
|
"amd": true,
|
||||||
|
"browser": true,
|
||||||
|
"es6": false
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-dupe-class-members": "off",
|
||||||
|
"no-var": "off",
|
||||||
|
"object-shorthand": "off",
|
||||||
|
"prefer-arrow-callback": "off",
|
||||||
|
"prefer-spread": "off",
|
||||||
|
"prefer-reflect": "off",
|
||||||
|
"prefer-template": "off"
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 5,
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"arrowFunctions": false,
|
||||||
|
"classes": false,
|
||||||
|
"defaultParams": false,
|
||||||
|
"destructuring": false,
|
||||||
|
"experimentalObjectRestSpread": false,
|
||||||
|
"blockBindings": false,
|
||||||
|
"forOf": false,
|
||||||
|
"generators": false,
|
||||||
|
"globalReturn": false,
|
||||||
|
"jsx": false,
|
||||||
|
"modules": false,
|
||||||
|
"objectLiteralComputedProperties": false,
|
||||||
|
"objectLiteralDuplicateProperties": false,
|
||||||
|
"objectLiteralShorthandMethods": false,
|
||||||
|
"objectLiteralShorthandProperties": false,
|
||||||
|
"impliedStrict": false,
|
||||||
|
"restParams": false,
|
||||||
|
"spread": false,
|
||||||
|
"superInFunctions": false,
|
||||||
|
"templateStrings": false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
84
public/.jshintrc
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"maxerr" : 50, // {int} Maximum error before stopping
|
||||||
|
|
||||||
|
// Enforcing
|
||||||
|
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
|
||||||
|
"camelcase" : false, // true: Identifiers must be in camelCase
|
||||||
|
"curly" : true, // true: Require {} for every new block or scope
|
||||||
|
"eqeqeq" : true, // true: Require triple equals (===) for comparison
|
||||||
|
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
|
||||||
|
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
|
||||||
|
"indent" : 4, // {int} Number of spaces to use for indentation
|
||||||
|
"latedef" : false, // true: Require variables/functions to be defined before being used
|
||||||
|
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
|
||||||
|
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
|
||||||
|
"noempty" : true, // true: Prohibit use of empty blocks
|
||||||
|
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
|
||||||
|
"plusplus" : false, // true: Prohibit use of `++` & `--`
|
||||||
|
"quotmark" : false, // Quotation mark consistency:
|
||||||
|
// false : do nothing (default)
|
||||||
|
// true : ensure whatever is used is consistent
|
||||||
|
// "single" : require single quotes
|
||||||
|
// "double" : require double quotes
|
||||||
|
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
|
||||||
|
"unused" : true, // true: Require all defined variables be used
|
||||||
|
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
|
||||||
|
"trailing" : false, // true: Prohibit trailing whitespaces
|
||||||
|
"maxparams" : false, // {int} Max number of formal params allowed per function
|
||||||
|
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
|
||||||
|
"maxstatements" : false, // {int} Max number statements per function
|
||||||
|
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
|
||||||
|
"maxlen" : false, // {int} Max number of characters per line
|
||||||
|
|
||||||
|
// Relaxing
|
||||||
|
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
|
||||||
|
"boss" : false, // true: Tolerate assignments where comparisons would be expected
|
||||||
|
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
|
||||||
|
"eqnull" : false, // true: Tolerate use of `== null`
|
||||||
|
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
|
||||||
|
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
|
||||||
|
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
|
||||||
|
// (ex: `for each`, multiple try/catch, function expression…)
|
||||||
|
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
|
||||||
|
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
|
||||||
|
"funcscope" : false, // true: Tolerate defining variables inside control statements"
|
||||||
|
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
|
||||||
|
"iterator" : false, // true: Tolerate using the `__iterator__` property
|
||||||
|
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
|
||||||
|
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
|
||||||
|
"laxcomma" : false, // true: Tolerate comma-first style coding
|
||||||
|
"loopfunc" : false, // true: Tolerate functions being defined in loops
|
||||||
|
"multistr" : false, // true: Tolerate multi-line strings
|
||||||
|
"proto" : false, // true: Tolerate using the `__proto__` property
|
||||||
|
"scripturl" : false, // true: Tolerate script-targeted URLs
|
||||||
|
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
|
||||||
|
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
|
||||||
|
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
|
||||||
|
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
|
||||||
|
"validthis" : false, // true: Tolerate using this in a non-constructor function
|
||||||
|
|
||||||
|
"globals": {
|
||||||
|
"app": true,
|
||||||
|
"io": true,
|
||||||
|
"socket": true,
|
||||||
|
"ajaxify": true,
|
||||||
|
"config": true,
|
||||||
|
"RELATIVE_PATH": true,
|
||||||
|
"utils": true,
|
||||||
|
"overrides": true,
|
||||||
|
"componentHandler": true,
|
||||||
|
"bootbox": true,
|
||||||
|
"templates": true,
|
||||||
|
"Visibility": true,
|
||||||
|
"Tinycon": true,
|
||||||
|
"require": true,
|
||||||
|
"define": true,
|
||||||
|
"ace": true,
|
||||||
|
"Sortable": true,
|
||||||
|
"Slideout": true,
|
||||||
|
"NProgress": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"jquery": true,
|
||||||
|
"browser": true
|
||||||
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Internal Server Error</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/assets/5xx.css" />
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
window.onload = function() {
|
|
||||||
let count = 0;
|
|
||||||
const bounce = document.getElementById('click-me');
|
|
||||||
bounce.onclick = function() {
|
|
||||||
count++;
|
|
||||||
bounce.className = '';
|
|
||||||
setTimeout(function() {
|
|
||||||
bounce.className = 'animated bounce';
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
if (count > 5) {
|
|
||||||
document.getElementById('hide').className = '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="center">
|
|
||||||
<h1 id="click-me" class="animated bounce">500</h1>
|
|
||||||
<p>
|
|
||||||
<strong>Internal server error. </strong>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{message}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<small id="hide" class="hide">Alright. You can stop clicking... it's not going to make the site come back sooner!</small>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
142
public/503.html
@@ -1,13 +1,149 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Excessive Load Warning</title>
|
<title>Excessive Load Warning</title>
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Ubuntu:400,500,700' rel='stylesheet' type='text/css'>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" type="text/css" href="/assets/5xx.css" />
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
background: #00A9EA;
|
||||||
|
color: white;
|
||||||
|
font-family: 'Ubuntu', sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
-webkit-transform-style: preserve-3d;
|
||||||
|
-moz-transform-style: preserve-3d;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 250px;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.5;
|
||||||
|
margin: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p strong {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p strong {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
-webkit-transform: translateY(50%);
|
||||||
|
-ms-transform: translateY(50%);
|
||||||
|
transform: translateY(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes bounce {
|
||||||
|
0%, 20%, 53%, 80%, 100% {
|
||||||
|
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||||
|
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||||
|
-webkit-transform: translate3d(0,0,0);
|
||||||
|
transform: translate3d(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40%, 43% {
|
||||||
|
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||||
|
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||||
|
-webkit-transform: translate3d(0, -30px, 0);
|
||||||
|
transform: translate3d(0, -30px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||||
|
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||||
|
-webkit-transform: translate3d(0, -15px, 0);
|
||||||
|
transform: translate3d(0, -15px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
90% {
|
||||||
|
-webkit-transform: translate3d(0,-4px,0);
|
||||||
|
transform: translate3d(0,-4px,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 20%, 53%, 80%, 100% {
|
||||||
|
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||||
|
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||||
|
-webkit-transform: translate3d(0,0,0);
|
||||||
|
transform: translate3d(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40%, 43% {
|
||||||
|
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||||
|
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||||
|
-webkit-transform: translate3d(0, -30px, 0);
|
||||||
|
transform: translate3d(0, -30px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||||
|
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||||
|
-webkit-transform: translate3d(0, -15px, 0);
|
||||||
|
transform: translate3d(0, -15px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
90% {
|
||||||
|
-webkit-transform: translate3d(0,-4px,0);
|
||||||
|
transform: translate3d(0,-4px,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bounce {
|
||||||
|
-webkit-animation-name: bounce;
|
||||||
|
animation-name: bounce;
|
||||||
|
-webkit-transform-origin: center bottom;
|
||||||
|
-ms-transform-origin: center bottom;
|
||||||
|
transform-origin: center bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated {
|
||||||
|
-webkit-animation-duration: 1s;
|
||||||
|
animation-duration: 1s;
|
||||||
|
-webkit-animation-fill-mode: both;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated.infinite {
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated.hinge {
|
||||||
|
-webkit-animation-duration: 2s;
|
||||||
|
animation-duration: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
let count = 0;
|
var count = 0,
|
||||||
const bounce = document.getElementById('click-me');
|
bounce = document.getElementById('click-me');
|
||||||
bounce.onclick = function() {
|
bounce.onclick = function() {
|
||||||
count++;
|
count++;
|
||||||
bounce.className = '';
|
bounce.className = '';
|
||||||
|
|||||||
135
public/5xx.css
@@ -1,135 +0,0 @@
|
|||||||
body {
|
|
||||||
background: #00A9EA;
|
|
||||||
color: white;
|
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
text-align: center;
|
|
||||||
-webkit-transform-style: preserve-3d;
|
|
||||||
-moz-transform-style: preserve-3d;
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 250px;
|
|
||||||
color: #fff;
|
|
||||||
opacity: 0.5;
|
|
||||||
margin: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-khtml-user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p strong {
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
h1 {
|
|
||||||
font-size: 125px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p strong {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
position: relative;
|
|
||||||
top: 50%;
|
|
||||||
-webkit-transform: translateY(50%);
|
|
||||||
-ms-transform: translateY(50%);
|
|
||||||
transform: translateY(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes bounce {
|
|
||||||
0%, 20%, 53%, 80%, 100% {
|
|
||||||
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
|
||||||
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
|
||||||
-webkit-transform: translate3d(0,0,0);
|
|
||||||
transform: translate3d(0,0,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
40%, 43% {
|
|
||||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
|
||||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
|
||||||
-webkit-transform: translate3d(0, -30px, 0);
|
|
||||||
transform: translate3d(0, -30px, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
70% {
|
|
||||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
|
||||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
|
||||||
-webkit-transform: translate3d(0, -15px, 0);
|
|
||||||
transform: translate3d(0, -15px, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
90% {
|
|
||||||
-webkit-transform: translate3d(0,-4px,0);
|
|
||||||
transform: translate3d(0,-4px,0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bounce {
|
|
||||||
0%, 20%, 53%, 80%, 100% {
|
|
||||||
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
|
||||||
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
|
||||||
-webkit-transform: translate3d(0,0,0);
|
|
||||||
transform: translate3d(0,0,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
40%, 43% {
|
|
||||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
|
||||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
|
||||||
-webkit-transform: translate3d(0, -30px, 0);
|
|
||||||
transform: translate3d(0, -30px, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
70% {
|
|
||||||
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
|
||||||
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
|
||||||
-webkit-transform: translate3d(0, -15px, 0);
|
|
||||||
transform: translate3d(0, -15px, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
90% {
|
|
||||||
-webkit-transform: translate3d(0,-4px,0);
|
|
||||||
transform: translate3d(0,-4px,0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bounce {
|
|
||||||
-webkit-animation-name: bounce;
|
|
||||||
animation-name: bounce;
|
|
||||||
-webkit-transform-origin: center bottom;
|
|
||||||
-ms-transform-origin: center bottom;
|
|
||||||
transform-origin: center bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animated {
|
|
||||||
-webkit-animation-duration: 1s;
|
|
||||||
animation-duration: 1s;
|
|
||||||
-webkit-animation-fill-mode: both;
|
|
||||||
animation-fill-mode: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animated.infinite {
|
|
||||||
-webkit-animation-iteration-count: infinite;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animated.hinge {
|
|
||||||
-webkit-animation-duration: 2s;
|
|
||||||
animation-duration: 2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/emails/banneduser.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
public/images/emails/digestheader.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
public/images/emails/emailconfirm.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/images/emails/invitation.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/images/emails/newtopic.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/images/emails/nodebb.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/images/emails/password.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/images/emails/triangularbackground.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/images/emails/unreadpost.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.9 KiB |
@@ -1,16 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="321" height="75" viewBox="0 0 321 75">
|
|
||||||
<defs>
|
|
||||||
<radialGradient id="nodebb-a" cx="65.599%" cy="0%" r="169.036%" fx="65.599%" fy="0%" gradientTransform="matrix(-.5183 .5916 -.38031 -.80624 .996 -.388)">
|
|
||||||
<stop offset="0%" stop-color="#2A6CBE"/>
|
|
||||||
<stop offset="38.688%" stop-color="#2062BC"/>
|
|
||||||
<stop offset="49.182%" stop-color="#1F5FBC"/>
|
|
||||||
<stop offset="66.583%" stop-color="#1C5ABD"/>
|
|
||||||
<stop offset="85.354%" stop-color="#1956BC"/>
|
|
||||||
<stop offset="100%" stop-color="#1851BE"/>
|
|
||||||
</radialGradient>
|
|
||||||
</defs>
|
|
||||||
<g fill="none" transform="translate(1 1)">
|
|
||||||
<path fill="#000306" stroke="#000306" stroke-width=".275" d="M195.810017,20 C210.691849,20 221.256813,28.7272272 221.484017,45.845679 C221.490864,46.3615394 221.461776,47.1194739 221.396754,48.1194827 C221.328352,49.1714504 220.455158,49.9897119 219.400969,49.9897119 L179.110557,49.9897119 C179.110557,56.6336943 182.723978,64.2757202 196.378026,64.2757202 C204.088827,64.2757202 211.625406,61.928339 218.987763,57.2335766 L219.553745,56.8677968 C220.246813,56.4139645 221.17656,56.6079034 221.630392,57.3009716 C221.757029,57.4943637 221.837254,57.7144077 221.864806,57.943925 C222.131013,60.1615294 222.004084,61.5832211 221.484017,62.2089999 C220.452051,63.4507301 210.901843,73 196.378026,73 C180.58738,73 169,61.8765432 169,46.3909465 C169,31.3415638 180.019372,20 195.810017,20 Z M162.92733,0 C164.031899,-4.2495073e-16 164.92733,0.8954305 164.92733,2 L164.928075,44.1266361 C164.975781,44.8118124 165,45.5058194 165,46.2079256 C165,61.5334332 153.460581,73 138,73 C122.427386,73 111,61.5334332 111,46.2079256 C111,30.882418 122.427386,19.4158511 138,19.4158511 C144.981278,19.4158511 151.163029,21.7538895 155.843826,25.7295173 L155.843557,4.54188628 C155.843557,2.03347175 157.877029,4.60788274e-16 160.385444,0 L162.92733,0 Z M81,20 C96.4605809,20 108,31.3415638 108,46.5 C108,61.6584362 96.4605809,73 81,73 C65.4273859,73 54,61.6584362 54,46.5 C54,31.3415638 65.4273859,20 81,20 Z M27.324263,20 C41.6099773,20 50,29.1649049 50,41.7801268 L50,67.5 C50,69.4329966 48.4329966,71 46.5,71 L42.2494331,71 C41.1448636,71 40.2494331,70.1045695 40.2494331,69 L40.2494331,42.5348837 C40.2494331,34.3403805 35.8276644,28.6257928 26.3038549,28.6257928 C10.0816327,28.6257928 9.75718821,35.7795705 9.75069932,44.6831843 L9.75056689,71 L2,71 C0.8954305,71 1.3527075e-16,70.1045695 0,69 L0,35.2932975 L0,35.2932975 C0,24.3935718 19.1609977,20 27.324263,20 Z M81,28.7242798 C70.8049793,28.7242798 63.6348548,36.4670782 63.6348548,46.5 C63.6348548,56.3148148 70.8049793,64.2757202 81,64.2757202 C91.1950207,64.2757202 98.3651452,56.3148148 98.3651452,46.5 C98.3651452,36.4670782 91.1950207,28.7242798 81,28.7242798 Z M138,28.2362872 C127.804979,28.2362872 120.634855,36.0644241 120.634855,46.2079256 C120.634855,56.1309161 127.804979,64.179564 138,64.179564 C148.195021,64.179564 155.365145,56.1309161 155.365145,46.2079256 C155.365145,36.0644241 148.195021,28.2362872 138,28.2362872 Z M195.810017,28.7242798 C186.721876,28.7242798 180.473779,33.9588477 179.224159,41.8106996 L211.259858,41.8106996 C210.010238,33.9588477 204.443752,28.7242798 195.810017,28.7242798 Z"/>
|
|
||||||
<path fill="url(#nodebb-a)" d="M277,19 L276.999615,57.7303365 C276.99251,64.180962 276.88949,68.6242617 276.88949,73 L276.88949,73 L255.649055,73 C242.962262,73 236,67.06 236,57.7257143 C236,51.4771429 239.558488,46.9257143 245.1283,45.0742857 C240.564149,43.1457143 237.779243,38.8257143 237.779243,33.5028571 C237.779243,24.8628571 244.664149,19 256.732074,19 L256.732074,19 L277,19 Z M299.267926,19 C311.335851,19 318.220757,24.8628571 318.220757,33.5028571 C318.220757,38.8257143 315.435851,43.1457143 310.8717,45.0742857 C316.441512,46.9257143 320,51.4771429 320,57.7257143 C320,67.06 313.037738,73 300.350945,73 L279.11051,73 L279.11051,73 C279.11051,68.6242617 279.00749,64.180962 279.000385,57.7303365 L279,19 L299.267926,19 Z M266.092452,49.8571429 L256.499999,49.8571429 C250.156602,49.8571429 246.984904,52.2485714 246.984904,56.9542857 C246.984904,61.8914286 249.924527,64.36 255.726414,64.36 L255.726414,64.36 L260.754716,64.36 C264.777358,64.36 266.092452,62.4314286 266.092452,56.5685714 L266.092452,56.5685714 L266.092452,49.8571429 Z M299.500001,49.8571429 L289.907548,49.8571429 L289.907548,56.5685714 C289.907548,62.4314286 291.222642,64.36 295.245284,64.36 L300.273586,64.36 C306.075473,64.36 309.015096,61.8914286 309.015096,56.9542857 C309.015096,52.2485714 305.843398,49.8571429 299.500001,49.8571429 Z M266.169811,27.64 L257.350942,27.64 C251.703772,27.64 248.764149,30.1857143 248.764149,34.5057143 C248.764149,38.8257143 251.703772,41.3714286 257.350942,41.3714286 L257.350942,41.3714286 L266.169811,41.3714286 L266.169811,27.64 Z M298.649058,27.64 L289.830189,27.64 L289.830189,41.3714286 L298.649058,41.3714286 C304.296228,41.3714286 307.235851,38.8257143 307.235851,34.5057143 C307.235851,30.1857143 304.296228,27.64 298.649058,27.64 Z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
7
public/js-enabled.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
The following stylesheet is only included on pages that can execute javascript
|
||||||
|
*/
|
||||||
|
|
||||||
|
.page-topic [component="post/content"] img:not(.not-responsive):not([data-state]) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# Important note about localising NodeBB
|
|
||||||
|
|
||||||
The files here are read-only and overwritten daily (if there are changes) by the
|
|
||||||
helper bot [Misty](https://github.com/nodebb-misty).
|
|
||||||
|
|
||||||
Our localisation efforts are handled via [our Transifex Project](https://explore.transifex.com/nodebb/nodebb/),
|
|
||||||
and any pull requests made to this directory will be automatically closed because
|
|
||||||
localisations can go out-of-sync when edited directly.
|
|
||||||
|
|
||||||
If there are non-localised strings and you cannot find them in Transifex, please
|
|
||||||
[open a new issue on our bug tracker](https://github.com/NodeBB/NodeBB/issues/new)
|
|
||||||
so we can take a look.
|
|
||||||
|
|
||||||
Thank you for helping localise NodeBB!
|
|
||||||
3
public/language/TODO
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
NPM INSTALL
|
||||||
|
For now, language packs will be stored here. Eventually, will be moved to server side to allow for npm install-ability.
|
||||||
|
When that happens, server code will generate compressed JSON language files in this folder.
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# The files here are not meant to be edited directly
|
|
||||||
|
|
||||||
Please see the → [Internalization README](../README.md).
|
|
||||||
@@ -1,18 +1,7 @@
|
|||||||
{
|
{
|
||||||
"alert.confirm-rebuild-and-restart": "هل أنت متأكد إنك تريد إعادة بناء وتشغيل الـ NodeBB؟",
|
"alert.confirm-reload": "Are you sure you wish to reload NodeBB?",
|
||||||
"alert.confirm-restart": "هل تريد بالتأكيد إعادة تشغيل NodeBB؟",
|
"alert.confirm-restart": "Are you sure you wish to restart NodeBB?",
|
||||||
|
|
||||||
"acp-title": "لوحة تحكم إدارة NodeBB | %1",
|
"acp-title": "%1 | NodeBB Admin Control Panel",
|
||||||
"settings-header-contents": "محتويات",
|
"settings-header-contents": "Contents"
|
||||||
"changes-saved": "Changes Saved",
|
|
||||||
"changes-saved-message": "Your changes to the NodeBB configuration have been saved.",
|
|
||||||
"changes-not-saved": "لم يتم حفظ التغييرات",
|
|
||||||
"changes-not-saved-message": "حدثت مشكلة أثناء حفظ التغييرات في NodeBB. (%1)",
|
|
||||||
"save-changes": "Save changes",
|
|
||||||
"min": "Min:",
|
|
||||||
"max": "Max:",
|
|
||||||
"view": "View",
|
|
||||||
"edit": "Edit",
|
|
||||||
"add": "Add",
|
|
||||||
"select-icon": "Select Icon"
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"cache": "Cache",
|
"post-cache": "Post Cache",
|
||||||
"post-cache": "التخزين المؤقت للمشاركات",
|
"posts-in-cache": "Posts in Cache",
|
||||||
"group-cache": "التخزين المؤقت للمجموعات",
|
"average-post-size": "Average Post Size",
|
||||||
"local-cache": "تخزين مؤقت محلي",
|
"length-to-max": "Length / Max",
|
||||||
"object-cache": "تخزين مؤقت للأشياء",
|
"percent-full": "%1% Full",
|
||||||
"percent-full": "1% كاملة",
|
"post-cache-size": "Post Cache Size",
|
||||||
"post-cache-size": "حجم التخزين المؤقت للمشاركات",
|
"items-in-cache": "Items in Cache",
|
||||||
"items-in-cache": "العناصر في التخزين المؤقت"
|
"control-panel": "Control Panel",
|
||||||
|
"update-settings": "Update Cache Settings"
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,36 @@
|
|||||||
{
|
{
|
||||||
"x-b": "٪1 بايت",
|
"x-b": "%1 b",
|
||||||
"x-mb": "٪1 ميقا بايت",
|
"x-mb": "%1 mb",
|
||||||
"x-gb": "٪1 قيقا بايت",
|
"x-gb": "%1 gb",
|
||||||
"uptime-seconds": "مدة التشغيل بالثواني",
|
"uptime-seconds": "Uptime in Seconds",
|
||||||
"uptime-days": "مدة التشغيل بالأيام",
|
"uptime-days": "Uptime in Days",
|
||||||
|
|
||||||
"mongo": "MongoDB",
|
"mongo": "Mongo",
|
||||||
"mongo.version": "اصدار MongoDB",
|
"mongo.version": "MongoDB Version",
|
||||||
"mongo.storage-engine": "محرك التخزين",
|
"mongo.storage-engine": "Storage Engine",
|
||||||
"mongo.collections": "التجميعات",
|
"mongo.collections": "Collections",
|
||||||
"mongo.objects": "العناصر",
|
"mongo.objects": "Objects",
|
||||||
"mongo.avg-object-size": "معدل حجم العناصر",
|
"mongo.avg-object-size": "Avg. Object Size",
|
||||||
"mongo.data-size": "حجم البيانات",
|
"mongo.data-size": "Data Size",
|
||||||
"mongo.storage-size": "حجم التخرين",
|
"mongo.storage-size": "Storage Size",
|
||||||
"mongo.index-size": "حجم الفهرس",
|
"mongo.index-size": "Index Size",
|
||||||
"mongo.file-size": "حجم الملف",
|
"mongo.file-size": "File Size",
|
||||||
"mongo.resident-memory": "الذاكرة الساكنة",
|
"mongo.resident-memory": "Resident Memory",
|
||||||
"mongo.virtual-memory": "الذاكرة الإفتراضية",
|
"mongo.virtual-memory": "Virtual Memory",
|
||||||
"mongo.mapped-memory": "Mapped Memory",
|
"mongo.mapped-memory": "Mapped Memory",
|
||||||
"mongo.bytes-in": "البايتات الواردة",
|
"mongo.raw-info": "MongoDB Raw Info",
|
||||||
"mongo.bytes-out": "البايتات الصادرة",
|
|
||||||
"mongo.num-requests": "عدد الطلبات",
|
|
||||||
"mongo.raw-info": "معلومات MongoDB الأولية",
|
|
||||||
"mongo.unauthorized": "لم يستطع NodeBB من الاستعلام عن احصاءات قواعد البيانات ل MongoDB. الرجاء التأكد من أن المستخدم في NodeBB يحتوي على دور "clusterMonitor" ال "admin" لقواعد البيانات.",
|
|
||||||
|
|
||||||
"redis": "Redis",
|
"redis": "Redis",
|
||||||
"redis.version": "إصدار Redis",
|
"redis.version": "Redis Version",
|
||||||
"redis.keys": "المفاتيح",
|
"redis.connected-clients": "Connected Clients",
|
||||||
"redis.expires": "ينتهي ",
|
"redis.connected-slaves": "Connected Slaves",
|
||||||
"redis.avg-ttl": "متوسط وقت الانعاش",
|
"redis.blocked-clients": "Blocked Clients",
|
||||||
"redis.connected-clients": "العميل المتصل",
|
"redis.used-memory": "Used Memory",
|
||||||
"redis.connected-slaves": "البدلاء المتصلين",
|
"redis.memory-frag-ratio": "Memory Fragmentation Ratio",
|
||||||
"redis.blocked-clients": "العملاء المحظورون",
|
"redis.total-connections-recieved": "Total Connections Received",
|
||||||
"redis.used-memory": "الذاكرة المستخدمة",
|
"redis.total-commands-processed": "Total Commands Processed",
|
||||||
"redis.memory-frag-ratio": "نسبة تجزئة الذاكرة",
|
"redis.iops": "Instantaneous Ops. Per Second",
|
||||||
"redis.total-connections-recieved": "إجمالي الاتصالات المستلمة",
|
"redis.keyspace-hits": "Keyspace Hits",
|
||||||
"redis.total-commands-processed": "إجمالي الأوامر التي تمت معالجتها",
|
"redis.keyspace-misses": "Keyspace Misses",
|
||||||
"redis.iops": "العمليات اللحظية في الثانية",
|
"redis.raw-info": "Redis Raw Info"
|
||||||
"redis.iinput": "الإدخال الفوري في الثانية",
|
|
||||||
"redis.ioutput": "المخرجات الفورية في الثانية",
|
|
||||||
"redis.total-input": "مجموع المدخلات",
|
|
||||||
"redis.total-output": "مجموع المخرجات",
|
|
||||||
|
|
||||||
"redis.keyspace-hits": "المفاتيح المضغوطة",
|
|
||||||
"redis.keyspace-misses": "المفاتيح المخفقة",
|
|
||||||
"redis.raw-info": "معلومات Redis الأولية",
|
|
||||||
|
|
||||||
"postgres": "Postgres",
|
|
||||||
"postgres.version": "إصدار PostgreSQL",
|
|
||||||
"postgres.raw-info": "معلومات Postgres الأولية"
|
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
{
|
{
|
||||||
"errors": "Errors",
|
"figure-x": "Figure %1",
|
||||||
"figure-x": "شكل %1",
|
"error-events-per-day": "<code>%1</code> events per day",
|
||||||
"error-events-per-day": "<code>%1</code> حدث كل يوم ",
|
"error.404": "404 Not Found",
|
||||||
"error.404": "404 لم يتم العثور",
|
"error.503": "503 Service Unavailable",
|
||||||
"error.503": "503 الخدمة غير متوفرة",
|
"manage-error-log": "Manage Error Log",
|
||||||
"manage-error-log": "إدارة سجل الأخطاء",
|
"export-error-log": "Export Error Log (CSV)",
|
||||||
"export-error-log": "تصدير سجل الأخطاء (CSV)",
|
"clear-error-log": "Clear Error Log",
|
||||||
"clear-error-log": "محو سجل الأخطاء",
|
"route": "Route",
|
||||||
"route": "مسار",
|
"count": "Count",
|
||||||
"count": "عدد",
|
"no-routes-not-found": "Hooray! No 404 errors!",
|
||||||
"no-routes-not-found": "لا توجد اخطاء 404!",
|
"clear404-confirm": "Are you sure you wish to clear the 404 error logs?",
|
||||||
"clear404-confirm": "هل تريد بالتأكيد محو سجلات الخطأ 404؟",
|
"clear404-success": "\"404 Not Found\" errors cleared"
|
||||||
"clear404-success": "أخطاء \"404 لم يتم العثور\" تم محوها بنجاح"
|
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,6 @@
|
|||||||
{
|
{
|
||||||
"events": "أحداث",
|
"events": "Events",
|
||||||
"no-events": "لا توجد أحداث",
|
"no-events": "There are no events",
|
||||||
"control-panel": "لوحة تحكم الأحداث",
|
"control-panel": "Events Control Panel",
|
||||||
"delete-events": "حذف الاحداث",
|
"delete-events": "Delete Events"
|
||||||
"confirm-delete-all-events": "هل أنت متاكد أنك تريد حذف كل سجل اﻻحداث؟",
|
|
||||||
"filters": "تصفية",
|
|
||||||
"filters-apply": "تطبيق التصفية",
|
|
||||||
"filter-type": "نوع الحدث",
|
|
||||||
"filter-start": "تاريخ البدء",
|
|
||||||
"filter-end": "تاريخ الانتهاء",
|
|
||||||
"filter-user": "Filter by User",
|
|
||||||
"filter-user.placeholder": "قم بكتابة اسم مستخدم للتصفية...",
|
|
||||||
"filter-group": "Filter by Group",
|
|
||||||
"filter-group.placeholder": "قم بكتابة اسم مجموعة للتصفية...",
|
|
||||||
"filter-per-page": "لكل صفحة"
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"logs": "السجلات",
|
"logs": "Logs",
|
||||||
"control-panel": "لوحة تحكم السجلات",
|
"control-panel": "Logs Control Panel",
|
||||||
"reload": "إعادة تحميل السجلات",
|
"reload": "Reload Logs",
|
||||||
"clear": "محو السجلات",
|
"clear": "Clear Logs",
|
||||||
"clear-success": "تم محو السجلات!"
|
"clear-success": "Logs Cleared!"
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,12 @@
|
|||||||
{
|
{
|
||||||
"customise": "Customise",
|
"custom-css": "Custom CSS",
|
||||||
"custom-css": "Custom CSS/SASS",
|
"custom-css.description": "Enter your own CSS declarations here, which will be applied after all other styles.",
|
||||||
"custom-css.description": "Enter your own CSS/SASS declarations here, which will be applied after all other styles.",
|
"custom-css.enable": "Enable Custom CSS",
|
||||||
"custom-css.enable": "Enable Custom CSS/SASS",
|
|
||||||
|
|
||||||
"custom-js": "Javascript مخصصة",
|
"custom-header": "Custom Header",
|
||||||
"custom-js.description": "أدخل Javascript الخاص بك هنا. سيتم تنفيذها بعد تحميل الصفحة بالكامل.",
|
"custom-header.description": "Enter custom HTML here (ex. JavaScript, Meta Tags, etc.), which will be appended to the <code><head></code> section of your forum's markup.",
|
||||||
"custom-js.enable": "تفعيل Javascript المخصصة",
|
"custom-header.enable": "Enable Custom Header",
|
||||||
|
|
||||||
"custom-header": "ترويسة مخصصة",
|
"custom-css.livereload": "Enable Live Reload",
|
||||||
"custom-header.description": "أدخل وسوم HTML المخصصة هنا (مثل: Meta Tags, وغيرها)، والتي سيتم تضمينها لجزئية <code><head></code> من ترميز المنتدى. يُسمح بعلامات البرمجة النصية، ولكن لا يُنصح بها ، نظرًا لأن علامة التبويب <a href=\"#custom-js\" data-toggle=\"tab\"> Custom Javascript </a> متاحة.",
|
"custom-css.livereload.description": "Enable this to force all sessions on every device under your account to refresh whenever you click save"
|
||||||
"custom-header.enable": "تفعيل الترويسة المخصصة",
|
|
||||||
|
|
||||||
"custom-css.livereload": "تفعيل إعادة التحميل المباشرة",
|
|
||||||
"custom-css.livereload.description": "فعل هذا الخيار لإجبار جميع الجلسات في الأجهزة التي قمت بتسجيل الدخول فيها بحسابك على التحديث عند النقر على زر الحفظ",
|
|
||||||
"bsvariables": "_variables.scss",
|
|
||||||
"bsvariables.description": "Override bootstrap variables here. You can also use a tool like <a href=\"https://bootstrap.build/app\">bootstrap.build</a> and paste the output here.<br/>Changes require a rebuild & restart.",
|
|
||||||
"bsvariables.enable": "Enable _variables.scss"
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,9 @@
|
|||||||
{
|
{
|
||||||
"skins": "Skins",
|
"loading": "Loading Skins...",
|
||||||
"bootswatch-skins": "Bootswatch Skins",
|
"homepage": "Homepage",
|
||||||
"custom-skins": "Custom Skins",
|
"select-skin": "Select Skin",
|
||||||
"add-skin": "Add Skin",
|
"current-skin": "Current Skin",
|
||||||
"save-custom-skins": "Save Custom Skins",
|
"skin-updated": "Skin Updated",
|
||||||
"save-custom-skins-success": "Custom skins saved successfully",
|
"applied-success": "%1 skin was succesfully applied",
|
||||||
"custom-skin-name": "Custom Skin Name",
|
"revert-success": "Skin reverted to base colours"
|
||||||
"custom-skin-variables": "Custom Skin Variables",
|
|
||||||
"loading": "جاري تحميل السمات...",
|
|
||||||
"homepage": "الصفحة الرئيسية",
|
|
||||||
"select-skin": "إختيار السمة",
|
|
||||||
"revert-skin": "Revert Skin",
|
|
||||||
"current-skin": "السمة الحالية",
|
|
||||||
"skin-updated": "تم تحديث السمة",
|
|
||||||
"applied-success": "تم تطبيق السمة %1 بنجاح",
|
|
||||||
"revert-success": "تم إستعادة الألوان الاساسية للسمة"
|
|
||||||
}
|
}
|
||||||