From 65e3a82d8be93777101df9aef0e1900a0527f267 Mon Sep 17 00:00:00 2001 From: Umputun Date: Tue, 5 Apr 2022 12:08:44 -0500 Subject: [PATCH] add no-signature mode suppressing app info middleware #1305 --- backend/app/cmd/server.go | 2 + backend/app/rest/api/rest.go | 6 +- backend/app/rest/api/rest_public_test.go | 29 ++- backend/app/rest/api/rest_test.go | 8 +- .../docs/configuration/parameters/index.md | 215 +++++++++--------- 5 files changed, 148 insertions(+), 112 deletions(-) diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index 8e74f956..b0d8b2d7 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -81,6 +81,7 @@ type ServerCommand struct { ProxyCORS bool `long:"proxy-cors" env:"PROXY_CORS" description:"disable internal CORS and delegate it to proxy"` AllowedHosts []string `long:"allowed-hosts" env:"ALLOWED_HOSTS" description:"limit hosts/sources allowed to embed comments"` SubscribersOnly bool `long:"subscribers-only" env:"SUBSCRIBERS_ONLY" description:"enable commenting only for Patreon subscribers"` + DisableSignature bool `long:"disable-signature" env:"DISABLE_SIGNATURE" description:"disable server signature in headers"` Auth struct { TTL struct { @@ -577,6 +578,7 @@ func (s *ServerCommand) newServerApp(ctx context.Context) (*serverApp, error) { AllowedAncestors: s.AllowedHosts, SendJWTHeader: s.Auth.SendJWTHeader, SubscribersOnly: s.SubscribersOnly, + DisableSignature: s.DisableSignature, } srv.ScoreThresholds.Low, srv.ScoreThresholds.Critical = s.LowScore, s.CriticalScore diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index 9c3309ab..4394417d 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -65,6 +65,7 @@ type Rest struct { SendJWTHeader bool AllowedAncestors []string // sets Content-Security-Policy "frame-ancestors ..." SubscribersOnly bool + DisableSignature bool // prevent signature from being added to headers SSLConfig SSLConfig httpsServer *http.Server @@ -191,7 +192,10 @@ func (s *Rest) makeHTTPServer(address string, port int, router http.Handler) *ht func (s *Rest) routes() chi.Router { router := chi.NewRouter() router.Use(middleware.Throttle(1000), middleware.RealIP, R.Recoverer(log.Default())) - router.Use(R.AppInfo("remark42", "umputun", s.Version), R.Ping) + if !s.DisableSignature { + router.Use(R.AppInfo("remark42", "umputun", s.Version)) + } + router.Use(R.Ping) s.pubRest, s.privRest, s.adminRest, s.rssRest = s.controllerGroups() // assign controllers for groups diff --git a/backend/app/rest/api/rest_public_test.go b/backend/app/rest/api/rest_public_test.go index 4071934d..8d813043 100644 --- a/backend/app/rest/api/rest_public_test.go +++ b/backend/app/rest/api/rest_public_test.go @@ -23,9 +23,32 @@ func TestRest_Ping(t *testing.T) { ts, _, teardown := startupT(t) defer teardown() - res, code := get(t, ts.URL+"/api/v1/ping") - assert.Equal(t, "pong", res) - assert.Equal(t, 200, code) + resp, err := http.Get(ts.URL + "/api/v1/ping") + require.NoError(t, err) + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + assert.Equal(t, "pong", string(body)) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, "remark42", resp.Header.Get("App-Name")) +} + +func TestRest_PingNoSignature(t *testing.T) { + ts, _, teardown := startupT(t, func(srv *Rest) { + srv.DisableSignature = true + }) + defer teardown() + + resp, err := http.Get(ts.URL + "/api/v1/ping") + require.NoError(t, err) + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + assert.Equal(t, "pong", string(body)) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, "", resp.Header.Get("App-Name")) } func TestRest_Preview(t *testing.T) { diff --git a/backend/app/rest/api/rest_test.go b/backend/app/rest/api/rest_test.go index e6e940e1..8fcbf2b1 100644 --- a/backend/app/rest/api/rest_test.go +++ b/backend/app/rest/api/rest_test.go @@ -412,7 +412,9 @@ func randomPath(tempDir, basename, suffix string) (string, error) { return "", errors.New("cannot create temp file") } -func startupT(t *testing.T) (ts *httptest.Server, srv *Rest, teardown func()) { +// startupT runs fully configured testing server +// srvHook is an optional func to set some Rest param after the creation but prior to Run +func startupT(t *testing.T, srvHook ...func(srv *Rest)) (ts *httptest.Server, srv *Rest, teardown func()) { tmp := os.TempDir() testDB, err := randomPath(tmp, "test-remark", ".db") require.NoError(t, err) @@ -477,6 +479,10 @@ func startupT(t *testing.T) (ts *httptest.Server, srv *Rest, teardown func()) { } srv.ScoreThresholds.Low, srv.ScoreThresholds.Critical = -5, -10 + for _, h := range srvHook { + h(srv) + } + ts = httptest.NewServer(srv.routes()) teardown = func() { diff --git a/site/src/docs/configuration/parameters/index.md b/site/src/docs/configuration/parameters/index.md index fbd8b4dd..53e7b23c 100644 --- a/site/src/docs/configuration/parameters/index.md +++ b/site/src/docs/configuration/parameters/index.md @@ -32,113 +32,114 @@ services: ### Complete parameters list -| Command line | Environment | Default | Description | -| ----------------------- | ----------------------- | ------------------------ | ----------------------------------------------- | -| url | REMARK_URL | | URL to Remark42 server, _required_ | -| secret | SECRET | | the shared secret key used to sign JWT, should be a random, long, hard-to-guess string, _required_ | -| site | SITE | `remark` | site name(s), _multi_ | -| store.type | STORE_TYPE | `bolt` | type of storage, `bolt` or `rpc` | -| store.bolt.path | STORE_BOLT_PATH | `./var` | parent directory for the bolt files | -| store.bolt.timeout | STORE_BOLT_TIMEOUT | `30s` | boltdb access timeout | -| admin.shared.id | ADMIN_SHARED_ID | | admin IDs (list of user IDs), _multi_ | -| admin.shared.email | ADMIN_SHARED_EMAIL | `admin@${REMARK_URL}` | admin emails, _multi_ | -| backup | BACKUP_PATH | `./var/backup` | backups location | -| max-back | MAX_BACKUP_FILES | `10` | max backup files to keep | -| cache.type | CACHE_TYPE | `mem` | type of cache, `redis_pub_sub` or `mem` or `none` | -| cache.redis_addr | CACHE_REDIS_ADDR | `127.0.0.1:6379` | address of Redis PubSub instance, turn `redis_pub_sub` cache on for distributed cache | -| cache.max.items | CACHE_MAX_ITEMS | `1000` | max number of cached items, `0` - unlimited | -| cache.max.value | CACHE_MAX_VALUE | `65536` | max size of the cached value, `0` - unlimited | -| cache.max.size | CACHE_MAX_SIZE | `50000000` | max size of all cached values, `0` - unlimited | -| avatar.type | AVATAR_TYPE | `fs` | type of avatar storage, `fs`, `bolt`, or `uri` | -| avatar.fs.path | AVATAR_FS_PATH | `./var/avatars` | avatars location for `fs` store | -| avatar.bolt.file | AVATAR_BOLT_FILE | `./var/avatars.db` | avatars `bolt` file location | -| avatar.uri | AVATAR_URI | `./var/avatars` | avatars store URI | -| avatar.rsz-lmt | AVATAR_RSZ_LMT | `0` (disabled) | max image size for resizing avatars on save | -| image.type | IMAGE_TYPE | `fs` | type of image storage, `fs`, `bolt` | -| image.max-size | IMAGE_MAX_SIZE | `5000000` | max size of image file | -| image.fs.path | IMAGE_FS_PATH | `./var/pictures` | permanent location of images | -| image.fs.staging | IMAGE_FS_STAGING | `./var/pictures.staging` | staging location of images | -| image.fs.partitions | IMAGE_FS_PARTITIONS | `100` | number of image partitions | -| image.bolt.file | IMAGE_BOLT_FILE | `/var/pictures.db` | images bolt file location | -| image.resize-width | IMAGE_RESIZE_WIDTH | `2400` | width of a resized image | -| image.resize-height | IMAGE_RESIZE_HEIGHT | `900` | height of a resized image | -| auth.ttl.jwt | AUTH_TTL_JWT | `5m` | JWT TTL | -| auth.ttl.cookie | AUTH_TTL_COOKIE | `200h` | cookie TTL | -| auth.send-jwt-header | AUTH_SEND_JWT_HEADER | `false` | send JWT as a header instead of a cookie | -| auth.same-site | AUTH_SAME_SITE | `default` | set same site policy for cookies (`default`, `none`, `lax` or `strict`) | -| auth.google.cid | AUTH_GOOGLE_CID | | Google OAuth client ID | -| auth.google.csec | AUTH_GOOGLE_CSEC | | Google OAuth client secret | -| auth.facebook.cid | AUTH_FACEBOOK_CID | | Facebook OAuth client ID | -| auth.facebook.csec | AUTH_FACEBOOK_CSEC | | Facebook OAuth client secret | -| auth.microsoft.cid | AUTH_MICROSOFT_CID | | Microsoft OAuth client ID | -| auth.microsoft.csec | AUTH_MICROSOFT_CSEC | | Microsoft OAuth client secret | -| auth.github.cid | AUTH_GITHUB_CID | | GitHub OAuth client ID | -| auth.github.csec | AUTH_GITHUB_CSEC | | GitHub OAuth client secret | -| auth.twitter.cid | AUTH_TWITTER_CID | | Twitter Consumer API Key | -| auth.twitter.csec | AUTH_TWITTER_CSEC | | Twitter Consumer API Secret key | -| auth.patreon.cid | AUTH_PATREON_CID | | Patreon OAuth Client ID | -| auth.patreon.csec | AUTH_PATREON_CSEC | | Patreon OAuth Client Secret | -| auth.telegram | AUTH_TELEGRAM | `false` | Enable Telegram auth (telegram.token must be present) | -| auth.yandex.cid | AUTH_YANDEX_CID | | Yandex OAuth client ID | -| auth.yandex.csec | AUTH_YANDEX_CSEC | | Yandex OAuth client secret | -| auth.dev | AUTH_DEV | `false` | local OAuth2 server, development mode only | -| auth.anon | AUTH_ANON | `false` | enable anonymous login | -| auth.email.enable | AUTH_EMAIL_ENABLE | `false` | enable auth via email | -| auth.email.from | AUTH_EMAIL_FROM | | email from | -| auth.email.subj | AUTH_EMAIL_SUBJ | `remark42 confirmation` | email subject | -| auth.email.content-type | AUTH_EMAIL_CONTENT_TYPE | `text/html` | email content type | -| notify.users | NOTIFY_USERS | none | type of user notifications (Telegram, email) | -| notify.admins | NOTIFY_ADMINS | none | type of admin notifications (Telegram, Slack, webhook and/or email) | -| notify.queue | NOTIFY_QUEUE | `100` | size of notification queue | -| notify.telegram.chan | NOTIFY_TELEGRAM_CHAN | | the ID of telegram channel for admin notifications | -| notify.slack.token | NOTIFY_SLACK_TOKEN | | Slack token | -| notify.slack.chan | NOTIFY_SLACK_CHAN | `general` | Slack channel for admin notifications | -| notify.webhook.url | NOTIFY_WEBHOOK_URL | | Webhook notification URL for admin notifications | -| notify.webhook.template | NOTIFY_WEBHOOK_TEMPLATE | `{"text": "{{.Text}}"}` | Webhook payload template | -| notify.webhook.headers | NOTIFY_WEBHOOK_HEADERS | | HTTP header in format Header1:Value1,Header2:Value2,...| -| notify.webhook.timeout | NOTIFY_WEBHOOK_TIMEOUT | `5s` | Webhook connection timeout | -| notify.email.fromAddress| NOTIFY_EMAIL_FROM | | from email address | -| notify.email.verification_subj | NOTIFY_EMAIL_VERIFICATION_SUBJ | `Email verification` | verification message subject | -| telegram.token | TELEGRAM_TOKEN | | Telegram token (used for auth and Telegram notifications) | -| telegram.timeout | TELEGRAM_TIMEOUT | `5s` | Telegram connection timeout | -| smtp.host | SMTP_HOST | | SMTP host | -| smtp.port | SMTP_PORT | | SMTP port | -| smtp.username | SMTP_USERNAME | | SMTP user name | -| smtp.password | SMTP_PASSWORD | | SMTP password | -| smtp.tls | SMTP_TLS | `false` | enable TLS for SMTP | -| smtp.timeout | SMTP_TIMEOUT | `10s` | SMTP TCP connection timeout | -| ssl.type | SSL_TYPE | none | `none`-HTTP, `static`-HTTPS, `auto`-HTTPS + le | -| ssl.port | SSL_PORT | `8443` | port for HTTPS server | -| ssl.cert | SSL_CERT | | path to the cert.pem file | -| ssl.key | SSL_KEY | | path to the key.pem file | -| ssl.acme-location | SSL_ACME_LOCATION | `./var/acme` | dir where obtained le-certs will be stored | -| ssl.acme-email | SSL_ACME_EMAIL | | admin email for receiving notifications from LE | -| max-comment | MAX_COMMENT_SIZE | `2048` | comment's size limit | -| max-votes | MAX_VOTES | `-1` | votes limit per comment, `-1` - unlimited | -| votes-ip | VOTES_IP | `false` | restrict votes from the same IP | -| anon-vote | ANON_VOTE | `false` | allow voting for anonymous users, require VOTES_IP to be enabled as well | -| votes-ip-time | VOTES_IP_TIME | `5m` | same IP vote restriction time, `0s` - unlimited | -| low-score | LOW_SCORE | `-5` | low score threshold | -| critical-score | CRITICAL_SCORE | `-10` | critical score threshold | -| positive-score | POSITIVE_SCORE | `false` | restricts comment's score to be only positive | -| restricted-words | RESTRICTED_WORDS | | words banned in comments (can use `*`), _multi_ | -| restricted-names | RESTRICTED_NAMES | | names prohibited to use by the user, _multi_ | -| edit-time | EDIT_TIME | `5m` | edit window | -| admin-edit | ADMIN_EDIT | `false` | unlimited edit for admins | -| read-age | READONLY_AGE | | read-only age of comments, days | -| image-proxy.http2https | IMAGE_PROXY_HTTP2HTTPS | `false` | enable HTTP->HTTPS proxy for images | -| image-proxy.cache-external | IMAGE_PROXY_CACHE_EXTERNAL | `false` | enable caching external images to current image storage | -| emoji | EMOJI | `false` | enable emoji support | -| simple-view | SIMPLE_VIEW | `false` | minimized UI with basic info only | -| proxy-cors | PROXY_CORS | `false` | disable internal CORS and delegate it to proxy | -| allowed-hosts | ALLOWED_HOSTS | enable all | limit hosts/sources allowed to embed comments | -| address | REMARK_ADDRESS | all interfaces | web server listening address | -| port | REMARK_PORT | `8080` | web server port | -| web-root | REMARK_WEB_ROOT | `./web` | web server root directory | -| update-limit | UPDATE_LIMIT | `0.5` | updates/sec limit | -| subscribers-only | SUBSCRIBERS_ONLY | `false` | enable commenting only for Patreon subscribers | -| admin-passwd | ADMIN_PASSWD | none (disabled) | password for `admin` basic auth | -| dbg | DEBUG | `false` | debug mode | +| Command line | Environment | Default | Description | +|--------------------------------|--------------------------------|--------------------------|-----------------------------------------------------------| +| url | REMARK_URL | | URL to Remark42 server, _required_ | +| secret | SECRET | | the shared secret key used to sign JWT, should be a random, long, hard-to-guess string, _required_ | +| site | SITE | `remark` | site name(s), _multi_ | +| store.type | STORE_TYPE | `bolt` | type of storage, `bolt` or `rpc` | +| store.bolt.path | STORE_BOLT_PATH | `./var` | parent directory for the bolt files | +| store.bolt.timeout | STORE_BOLT_TIMEOUT | `30s` | boltdb access timeout | +| admin.shared.id | ADMIN_SHARED_ID | | admin IDs (list of user IDs), _multi_ | +| admin.shared.email | ADMIN_SHARED_EMAIL | `admin@${REMARK_URL}` | admin emails, _multi_ | +| backup | BACKUP_PATH | `./var/backup` | backups location | +| max-back | MAX_BACKUP_FILES | `10` | max backup files to keep | +| cache.type | CACHE_TYPE | `mem` | type of cache, `redis_pub_sub` or `mem` or `none` | +| cache.redis_addr | CACHE_REDIS_ADDR | `127.0.0.1:6379` | address of Redis PubSub instance, turn `redis_pub_sub` cache on for distributed cache | +| cache.max.items | CACHE_MAX_ITEMS | `1000` | max number of cached items, `0` - unlimited | +| cache.max.value | CACHE_MAX_VALUE | `65536` | max size of the cached value, `0` - unlimited | +| cache.max.size | CACHE_MAX_SIZE | `50000000` | max size of all cached values, `0` - unlimited | +| avatar.type | AVATAR_TYPE | `fs` | type of avatar storage, `fs`, `bolt`, or `uri` | +| avatar.fs.path | AVATAR_FS_PATH | `./var/avatars` | avatars location for `fs` store | +| avatar.bolt.file | AVATAR_BOLT_FILE | `./var/avatars.db` | avatars `bolt` file location | +| avatar.uri | AVATAR_URI | `./var/avatars` | avatars store URI | +| avatar.rsz-lmt | AVATAR_RSZ_LMT | `0` (disabled) | max image size for resizing avatars on save | +| image.type | IMAGE_TYPE | `fs` | type of image storage, `fs`, `bolt` | +| image.max-size | IMAGE_MAX_SIZE | `5000000` | max size of image file | +| image.fs.path | IMAGE_FS_PATH | `./var/pictures` | permanent location of images | +| image.fs.staging | IMAGE_FS_STAGING | `./var/pictures.staging` | staging location of images | +| image.fs.partitions | IMAGE_FS_PARTITIONS | `100` | number of image partitions | +| image.bolt.file | IMAGE_BOLT_FILE | `/var/pictures.db` | images bolt file location | +| image.resize-width | IMAGE_RESIZE_WIDTH | `2400` | width of a resized image | +| image.resize-height | IMAGE_RESIZE_HEIGHT | `900` | height of a resized image | +| auth.ttl.jwt | AUTH_TTL_JWT | `5m` | JWT TTL | +| auth.ttl.cookie | AUTH_TTL_COOKIE | `200h` | cookie TTL | +| auth.send-jwt-header | AUTH_SEND_JWT_HEADER | `false` | send JWT as a header instead of a cookie | +| auth.same-site | AUTH_SAME_SITE | `default` | set same site policy for cookies (`default`, `none`, `lax` or `strict`) | +| auth.google.cid | AUTH_GOOGLE_CID | | Google OAuth client ID | +| auth.google.csec | AUTH_GOOGLE_CSEC | | Google OAuth client secret | +| auth.facebook.cid | AUTH_FACEBOOK_CID | | Facebook OAuth client ID | +| auth.facebook.csec | AUTH_FACEBOOK_CSEC | | Facebook OAuth client secret | +| auth.microsoft.cid | AUTH_MICROSOFT_CID | | Microsoft OAuth client ID | +| auth.microsoft.csec | AUTH_MICROSOFT_CSEC | | Microsoft OAuth client secret | +| auth.github.cid | AUTH_GITHUB_CID | | GitHub OAuth client ID | +| auth.github.csec | AUTH_GITHUB_CSEC | | GitHub OAuth client secret | +| auth.twitter.cid | AUTH_TWITTER_CID | | Twitter Consumer API Key | +| auth.twitter.csec | AUTH_TWITTER_CSEC | | Twitter Consumer API Secret key | +| auth.patreon.cid | AUTH_PATREON_CID | | Patreon OAuth Client ID | +| auth.patreon.csec | AUTH_PATREON_CSEC | | Patreon OAuth Client Secret | +| auth.telegram | AUTH_TELEGRAM | `false` | Enable Telegram auth (telegram.token must be present) | +| auth.yandex.cid | AUTH_YANDEX_CID | | Yandex OAuth client ID | +| auth.yandex.csec | AUTH_YANDEX_CSEC | | Yandex OAuth client secret | +| auth.dev | AUTH_DEV | `false` | local OAuth2 server, development mode only | +| auth.anon | AUTH_ANON | `false` | enable anonymous login | +| auth.email.enable | AUTH_EMAIL_ENABLE | `false` | enable auth via email | +| auth.email.from | AUTH_EMAIL_FROM | | email from | +| auth.email.subj | AUTH_EMAIL_SUBJ | `remark42 confirmation` | email subject | +| auth.email.content-type | AUTH_EMAIL_CONTENT_TYPE | `text/html` | email content type | +| notify.users | NOTIFY_USERS | none | type of user notifications (Telegram, email) | +| notify.admins | NOTIFY_ADMINS | none | type of admin notifications (Telegram, Slack, webhook and/or email) | +| notify.queue | NOTIFY_QUEUE | `100` | size of notification queue | +| notify.telegram.chan | NOTIFY_TELEGRAM_CHAN | | the ID of telegram channel for admin notifications | +| notify.slack.token | NOTIFY_SLACK_TOKEN | | Slack token | +| notify.slack.chan | NOTIFY_SLACK_CHAN | `general` | Slack channel for admin notifications | +| notify.webhook.url | NOTIFY_WEBHOOK_URL | | Webhook notification URL for admin notifications | +| notify.webhook.template | NOTIFY_WEBHOOK_TEMPLATE | `{"text": "{{.Text}}"}` | Webhook payload template | +| notify.webhook.headers | NOTIFY_WEBHOOK_HEADERS | | HTTP header in format Header1:Value1,Header2:Value2,... | +| notify.webhook.timeout | NOTIFY_WEBHOOK_TIMEOUT | `5s` | Webhook connection timeout | +| notify.email.fromAddress | NOTIFY_EMAIL_FROM | | from email address | +| notify.email.verification_subj | NOTIFY_EMAIL_VERIFICATION_SUBJ | `Email verification` | verification message subject | +| telegram.token | TELEGRAM_TOKEN | | Telegram token (used for auth and Telegram notifications) | +| telegram.timeout | TELEGRAM_TIMEOUT | `5s` | Telegram connection timeout | +| smtp.host | SMTP_HOST | | SMTP host | +| smtp.port | SMTP_PORT | | SMTP port | +| smtp.username | SMTP_USERNAME | | SMTP user name | +| smtp.password | SMTP_PASSWORD | | SMTP password | +| smtp.tls | SMTP_TLS | `false` | enable TLS for SMTP | +| smtp.timeout | SMTP_TIMEOUT | `10s` | SMTP TCP connection timeout | +| ssl.type | SSL_TYPE | none | `none`-HTTP, `static`-HTTPS, `auto`-HTTPS + le | +| ssl.port | SSL_PORT | `8443` | port for HTTPS server | +| ssl.cert | SSL_CERT | | path to the cert.pem file | +| ssl.key | SSL_KEY | | path to the key.pem file | +| ssl.acme-location | SSL_ACME_LOCATION | `./var/acme` | dir where obtained le-certs will be stored | +| ssl.acme-email | SSL_ACME_EMAIL | | admin email for receiving notifications from LE | +| max-comment | MAX_COMMENT_SIZE | `2048` | comment's size limit | +| max-votes | MAX_VOTES | `-1` | votes limit per comment, `-1` - unlimited | +| votes-ip | VOTES_IP | `false` | restrict votes from the same IP | +| anon-vote | ANON_VOTE | `false` | allow voting for anonymous users, require VOTES_IP to be enabled as well | +| votes-ip-time | VOTES_IP_TIME | `5m` | same IP vote restriction time, `0s` - unlimited | +| low-score | LOW_SCORE | `-5` | low score threshold | +| critical-score | CRITICAL_SCORE | `-10` | critical score threshold | +| positive-score | POSITIVE_SCORE | `false` | restricts comment's score to be only positive | +| restricted-words | RESTRICTED_WORDS | | words banned in comments (can use `*`), _multi_ | +| restricted-names | RESTRICTED_NAMES | | names prohibited to use by the user, _multi_ | +| edit-time | EDIT_TIME | `5m` | edit window | +| admin-edit | ADMIN_EDIT | `false` | unlimited edit for admins | +| read-age | READONLY_AGE | | read-only age of comments, days | +| image-proxy.http2https | IMAGE_PROXY_HTTP2HTTPS | `false` | enable HTTP->HTTPS proxy for images | +| image-proxy.cache-external | IMAGE_PROXY_CACHE_EXTERNAL | `false` | enable caching external images to current image storage | +| emoji | EMOJI | `false` | enable emoji support | +| simple-view | SIMPLE_VIEW | `false` | minimized UI with basic info only | +| proxy-cors | PROXY_CORS | `false` | disable internal CORS and delegate it to proxy | +| allowed-hosts | ALLOWED_HOSTS | enable all | limit hosts/sources allowed to embed comments | +| address | REMARK_ADDRESS | all interfaces | web server listening address | +| port | REMARK_PORT | `8080` | web server port | +| web-root | REMARK_WEB_ROOT | `./web` | web server root directory | +| update-limit | UPDATE_LIMIT | `0.5` | updates/sec limit | +| subscribers-only | SUBSCRIBERS_ONLY | `false` | enable commenting only for Patreon subscribers | +| disable-signature | DISABLE_SIGNATURE | `false` | disable server signature in headers | +| admin-passwd | ADMIN_PASSWD | none (disabled) | password for `admin` basic auth | +| dbg | DEBUG | `false` | debug mode | - command-line parameters are long-form `--=value`, i.e., `--site=https://demo.remark42.com` - _multi_ parameters separated by `,` in the environment or repeated with command-line keys, like `--site=s1 --site=s2 ...`