fix: JupyterHub Dockerfile - add eric user, sudo, fix DB path (4 slashes)
This commit is contained in:
6
configuration/ditto/Dockerfile
Normal file
6
configuration/ditto/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM eclipse/ditto-gateway:latest
|
||||||
|
|
||||||
|
USER root
|
||||||
|
# Copy the modified JAR (with open auth in reference.conf)
|
||||||
|
COPY ditto-gateway-service-3.8.12-allinone.jar /opt/ditto/ditto-gateway-service-3.8.12-allinone.jar
|
||||||
|
USER ditto
|
||||||
22
configuration/ditto/application.conf
Normal file
22
configuration/ditto/application.conf
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Minimal override - authentication settings only
|
||||||
|
# This file is loaded after reference.conf by Typesafe config
|
||||||
|
|
||||||
|
ditto {
|
||||||
|
gateway {
|
||||||
|
authentication {
|
||||||
|
pre-authentication {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
devops {
|
||||||
|
secured = false
|
||||||
|
devops-authentication-method = "basic"
|
||||||
|
password = "ditto-devops-secret"
|
||||||
|
password = ${?DEVOPS_PASSWORD}
|
||||||
|
status-secured = false
|
||||||
|
status-authentication-method = "basic"
|
||||||
|
statusPassword = "ditto-status-secret"
|
||||||
|
statusPassword = ${?STATUS_PASSWORD}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
configuration/ditto/ditto-gateway-service-3.8.12-allinone.jar
Normal file
BIN
configuration/ditto/ditto-gateway-service-3.8.12-allinone.jar
Normal file
Binary file not shown.
7
configuration/ditto/docker-entrypoint.sh
Normal file
7
configuration/ditto/docker-entrypoint.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Fix permissions for mounted config files
|
||||||
|
if [ -f /opt/ditto/gateway-extension.conf ]; then
|
||||||
|
chmod 644 /opt/ditto/gateway-extension.conf
|
||||||
|
fi
|
||||||
|
# Start the gateway
|
||||||
|
exec java -jar /opt/ditto/ditto-gateway-service-3.8.12-allinone.jar
|
||||||
2
configuration/ditto/entrypoint.sh
Normal file
2
configuration/ditto/entrypoint.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
exec java -Dconfig.file=/opt/ditto/gateway.conf -jar /opt/ditto/ditto-gateway-service-3.8.12-allinone.jar
|
||||||
76
configuration/ditto/fix-jar.sh
Normal file
76
configuration/ditto/fix-jar.sh
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
WORKDIR=/tmp/ditto-jar-mod
|
||||||
|
rm -rf $WORKDIR && mkdir -p $WORKDIR
|
||||||
|
|
||||||
|
echo "=== Extracting JAR ==="
|
||||||
|
cd $WORKDIR
|
||||||
|
docker run --rm eclipse/ditto-gateway:latest cat /opt/ditto/ditto-gateway-service-3.8.12-allinone.jar > ditto-gateway-service-3.8.12-allinone.jar
|
||||||
|
jar xf ditto-gateway-service-3.8.12-allinone.jar reference.conf
|
||||||
|
|
||||||
|
echo "=== Original reference.conf tail ==="
|
||||||
|
tail -5 reference.conf
|
||||||
|
|
||||||
|
echo "=== Adding auth config to reference.conf ==="
|
||||||
|
# Remove the last closing brace, add our config, re-add the closing brace
|
||||||
|
# The reference.conf ends with nested braces - find the very last line
|
||||||
|
python3 << 'PYEOF'
|
||||||
|
with open("/tmp/ditto-jar-mod/reference.conf", "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Find the last non-empty line that is just a closing brace
|
||||||
|
# We need to insert our config before the outermost closing brace
|
||||||
|
|
||||||
|
# Simple approach: append before the very last }
|
||||||
|
# Count total closing braces at the end
|
||||||
|
content = "".join(lines)
|
||||||
|
|
||||||
|
# The reference.conf has a complex nested structure
|
||||||
|
# We'll add our ditto config as a new root-level block at the end
|
||||||
|
# We need to close the last block and add a comma, then our new block
|
||||||
|
|
||||||
|
# Actually, simpler: just append if the file ends with }
|
||||||
|
# Find the position of the very last }
|
||||||
|
last_brace_pos = content.rfind('}')
|
||||||
|
if last_brace_pos >= 0:
|
||||||
|
# Check if there's content after the last } (like kamon.conf include)
|
||||||
|
rest = content[last_brace_pos+1:].strip()
|
||||||
|
if rest:
|
||||||
|
# There's content after the last }, our approach is wrong
|
||||||
|
print(f"Content after last }}: {rest[:100]}")
|
||||||
|
# Insert before the last } with a comma
|
||||||
|
new_content = content[:last_brace_pos].rstrip()
|
||||||
|
# Remove trailing comma if present
|
||||||
|
if new_content.endswith(','):
|
||||||
|
new_content = new_content[:-1]
|
||||||
|
new_content += ',\n\n# Custom auth overrides\n' + rest[:0] + '\n' + ' authentication {\n pre-authentication {\n enabled = true\n }\n devops {\n secured = false\n }\n }\n}\n'
|
||||||
|
# Just append
|
||||||
|
new_content = content.rstrip() + '\n\n# Custom auth overrides\nditto {\n gateway {\n authentication {\n pre-authentication {\n enabled = true\n }\n devops {\n secured = false\n }\n }\n }\n}\n'
|
||||||
|
else:
|
||||||
|
# Last char is }, replace it with our config + }
|
||||||
|
new_content = content[:last_brace_pos].rstrip()
|
||||||
|
# Remove trailing comma
|
||||||
|
if new_content.endswith(','):
|
||||||
|
new_content = new_content[:-1]
|
||||||
|
new_content += ',\n\n# Custom auth overrides\n gateway {\n authentication {\n pre-authentication {\n enabled = true\n }\n devops {\n secured = false\n }\n }\n }\n}\n'
|
||||||
|
else:
|
||||||
|
new_content = content
|
||||||
|
|
||||||
|
with open("/tmp/ditto-jar-mod/reference.conf", "w") as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
print("reference.conf modified successfully")
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
echo "=== Modified reference.conf tail ==="
|
||||||
|
tail -20 reference.conf
|
||||||
|
|
||||||
|
echo "=== Updating JAR (replacing reference.conf) ==="
|
||||||
|
jar uf ditto-gateway-service-3.8.12-allinone.jar reference.conf
|
||||||
|
|
||||||
|
echo "=== Verifying modified reference.conf in JAR ==="
|
||||||
|
jar xf ditto-gateway-service-3.8.12-allinone.jar reference.conf
|
||||||
|
grep -c "pre-authentication" reference.conf || echo "NOT FOUND in JAR"
|
||||||
|
|
||||||
|
echo "=== Done. JAR is ready at $WORKDIR ==="
|
||||||
7
configuration/ditto/gateway-extension.conf
Normal file
7
configuration/ditto/gateway-extension.conf
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ditto {
|
||||||
|
gateway {
|
||||||
|
http {
|
||||||
|
enablecors = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
363
configuration/ditto/gateway.conf
Normal file
363
configuration/ditto/gateway.conf
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
ditto {
|
||||||
|
version = "3.8.12"
|
||||||
|
|
||||||
|
extensions {
|
||||||
|
jwt-authorization-subjects-provider = {
|
||||||
|
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DittoJwtAuthorizationSubjectsProvider
|
||||||
|
}
|
||||||
|
jwt-authentication-result-provider = {
|
||||||
|
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DefaultJwtAuthenticationResultProvider
|
||||||
|
extension-config = {
|
||||||
|
role = regular
|
||||||
|
jwt-authorization-subjects-provider = {
|
||||||
|
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DittoJwtAuthorizationSubjectsProvider
|
||||||
|
extension-config = {
|
||||||
|
role = regular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jwt-authentication-result-provider-devops = {
|
||||||
|
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DefaultJwtAuthenticationResultProvider
|
||||||
|
extension-config = {
|
||||||
|
role = devops
|
||||||
|
jwt-authorization-subjects-provider = {
|
||||||
|
extension-class = org.eclipse.ditto.gateway.service.security.authentication.jwt.DittoJwtAuthorizationSubjectsProvider
|
||||||
|
extension-config = {
|
||||||
|
role = devops
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signal-enrichment-provider {
|
||||||
|
extension-class = org.eclipse.ditto.gateway.service.endpoints.utils.DefaultGatewaySignalEnrichmentProvider
|
||||||
|
extension-config = {
|
||||||
|
cache {
|
||||||
|
enabled = true
|
||||||
|
maximum-size = 20000
|
||||||
|
expire-after-create = 2m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http-bind-flow-provider = org.eclipse.ditto.gateway.service.endpoints.routes.LoggingHttpBindFlowProvider
|
||||||
|
websocket-config-provider = org.eclipse.ditto.gateway.service.endpoints.routes.websocket.NoOpWebSocketConfigProvider
|
||||||
|
gateway-authentication-directive-factory = org.eclipse.ditto.gateway.service.endpoints.directives.auth.DittoGatewayAuthenticationDirectiveFactory
|
||||||
|
http-request-actor-props-factory = org.eclipse.ditto.gateway.service.endpoints.actors.DefaultHttpRequestActorPropsFactory
|
||||||
|
sse-event-sniffer = org.eclipse.ditto.gateway.service.endpoints.routes.sse.NoOpSseEventSniffer
|
||||||
|
streaming-authorization-enforcer = org.eclipse.ditto.gateway.service.streaming.NoOpAuthorizationEnforcer
|
||||||
|
incoming-websocket-event-sniffer = org.eclipse.ditto.gateway.service.endpoints.routes.websocket.NoOpIncomingWebSocketEventSniffer
|
||||||
|
outgoing-websocket-event-sniffer = org.eclipse.ditto.gateway.service.endpoints.routes.websocket.NoOpOutgoingWebSocketEventSniffer
|
||||||
|
custom-api-routes-provider = org.eclipse.ditto.gateway.service.endpoints.routes.NoopCustomApiRoutesProvider
|
||||||
|
sse-connection-supervisor = org.eclipse.ditto.gateway.service.endpoints.routes.sse.NoOpSseConnectionSupervisor
|
||||||
|
websocket-connection-supervisor = "org.eclipse.ditto.gateway.service.endpoints.routes.websocket.NoOpWebSocketSupervisor"
|
||||||
|
connections-retrieval-actor-props-factory = org.eclipse.ditto.gateway.service.endpoints.actors.DefaultConnectionsRetrievalActorPropsFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
service-name = "gateway"
|
||||||
|
mapping-strategy.implementation = "org.eclipse.ditto.gateway.service.util.GatewayMappingStrategies"
|
||||||
|
|
||||||
|
gateway {
|
||||||
|
http {
|
||||||
|
hostname = ""
|
||||||
|
hostname = ${?HOSTNAME}
|
||||||
|
hostname = ${?BIND_HOSTNAME}
|
||||||
|
port = 8080
|
||||||
|
port = ${?HTTP_PORT}
|
||||||
|
port = ${?PORT}
|
||||||
|
coordinated-shutdown-timeout = 65s
|
||||||
|
coordinated-shutdown-timeout = ${?COORDINATED_SHUTDOWN_REQUEST_TIMEOUT}
|
||||||
|
schema-versions = [2]
|
||||||
|
protocol-headers = ["X-Forwarded-Proto", "x_forwarded_proto"]
|
||||||
|
forcehttps = false
|
||||||
|
forcehttps = ${?FORCE_HTTPS}
|
||||||
|
redirect-to-https = false
|
||||||
|
redirect-to-https = ${?REDIRECT_TO_HTTPS}
|
||||||
|
redirect-to-https-blocklist-pattern = "/api.*|/ws.*|/status.*|/overall.*"
|
||||||
|
enablecors = false
|
||||||
|
enablecors = ${?ENABLE_CORS}
|
||||||
|
request-timeout = 60s
|
||||||
|
request-timeout = ${?REQUEST_TIMEOUT}
|
||||||
|
additional-accepted-media-types = ${?ADDITIONAL_ACCEPTED_MEDIA_TYPES}
|
||||||
|
query-params-as-headers = [
|
||||||
|
"accept"
|
||||||
|
"channel"
|
||||||
|
"correlation-id"
|
||||||
|
"requested-acks"
|
||||||
|
"declared-acks"
|
||||||
|
"response-required"
|
||||||
|
"timeout"
|
||||||
|
"live-channel-timeout-strategy"
|
||||||
|
"allow-policy-lockout"
|
||||||
|
"condition"
|
||||||
|
"live-channel-condition"
|
||||||
|
"at-historical-revision"
|
||||||
|
"at-historical-timestamp"
|
||||||
|
"dry-run"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
streaming {
|
||||||
|
session-counter-scrape-interval = 30s
|
||||||
|
parallelism = 64
|
||||||
|
parallelism = ${?GATEWAY_STREAMING_PARALLELISM}
|
||||||
|
search-idle-timeout = 60s
|
||||||
|
search-idle-timeout = ${?GATEWAY_STREAMING_SEARCH_IDLE_TIMEOUT}
|
||||||
|
subscription-refresh-delay = 5m
|
||||||
|
subscription-refresh-delay = ${?GATEWAY_STREAMING_SUBSCRIPTION_REFRESH_DELAY}
|
||||||
|
acknowledgement {
|
||||||
|
forwarder-fallback-timeout = 65s
|
||||||
|
}
|
||||||
|
websocket {
|
||||||
|
subscriber {
|
||||||
|
backpressure-queue-size = 100
|
||||||
|
}
|
||||||
|
publisher {
|
||||||
|
backpressure-buffer-size = 200
|
||||||
|
}
|
||||||
|
throttling-rejection-factor = 1.25
|
||||||
|
throttling {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
streaming-authorization-enforcer = "org.eclipse.ditto.gateway.service.streaming.NoOpAuthorizationEnforcer"
|
||||||
|
}
|
||||||
|
sse {
|
||||||
|
throttling {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
streaming-authorization-enforcer = "org.eclipse.ditto.gateway.service.streaming.NoOpAuthorizationEnforcer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command {
|
||||||
|
default-timeout = ${ditto.gateway.http.request-timeout}
|
||||||
|
max-timeout = 1m
|
||||||
|
smart-channel-buffer = 10s
|
||||||
|
connections-retrieve-limit = 100
|
||||||
|
}
|
||||||
|
message {
|
||||||
|
default-timeout = 10s
|
||||||
|
max-timeout = 1m
|
||||||
|
}
|
||||||
|
claim-message {
|
||||||
|
default-timeout = 1m
|
||||||
|
max-timeout = 10m
|
||||||
|
}
|
||||||
|
dns {
|
||||||
|
address = none
|
||||||
|
address = ${?DNS_SERVER}
|
||||||
|
}
|
||||||
|
|
||||||
|
authentication {
|
||||||
|
http {
|
||||||
|
proxy {
|
||||||
|
enabled = false
|
||||||
|
enabled = ${?AUTH_HTTP_PROXY_ENABLED}
|
||||||
|
hostname = ${?AUTH_HTTP_PROXY_HOST}
|
||||||
|
port = ${?AUTH_HTTP_PROXY_PORT}
|
||||||
|
username = ${?AUTH_HTTP_PROXY_USERNAME}
|
||||||
|
password = ${?AUTH_HTTP_PROXY_PASSWORD}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oauth {
|
||||||
|
protocol = "https"
|
||||||
|
protocol = ${?OAUTH_PROTOCOL}
|
||||||
|
allowed-clock-skew = 10s
|
||||||
|
allowed-clock-skew = ${?OAUTH_ALLOWED_CLOCK_SKEW}
|
||||||
|
openid-connect-issuers = {
|
||||||
|
google = {
|
||||||
|
issuer = "accounts.google.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
token-integration-subject = "integration:{{policy-entry:label}}:{{jwt:aud}}"
|
||||||
|
token-integration-subject = ${?OAUTH_TOKEN_INTEGRATION_SUBJECT}
|
||||||
|
}
|
||||||
|
|
||||||
|
# PRE-AUTHENTICATION = open access for /api/2/
|
||||||
|
pre-authentication {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
devops {
|
||||||
|
secured = false
|
||||||
|
devops-authentication-method = "basic"
|
||||||
|
password = "ditto-devops-secret"
|
||||||
|
password = ${?DEVOPS_PASSWORD}
|
||||||
|
status-secured = false
|
||||||
|
status-authentication-method = "basic"
|
||||||
|
statusPassword = "ditto-status-secret"
|
||||||
|
statusPassword = ${?STATUS_PASSWORD}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
health-check {
|
||||||
|
enabled = true
|
||||||
|
enabled = ${?HEALTH_CHECK_ENABLED}
|
||||||
|
interval = 60s
|
||||||
|
interval = ${?HEALTH_CHECK_INTERVAL}
|
||||||
|
service.timeout = 10s
|
||||||
|
service.timeout = ${?HEALTH_CHECK_SERVICE_TIMEOUT}
|
||||||
|
cluster-roles = {
|
||||||
|
enabled = true
|
||||||
|
enabled = ${?HEALTH_CHECK_ROLES_ENABLED}
|
||||||
|
expected = [
|
||||||
|
"policies"
|
||||||
|
"things"
|
||||||
|
"search"
|
||||||
|
"gateway"
|
||||||
|
"connectivity"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public-health {
|
||||||
|
cache-timeout = 20s
|
||||||
|
cache-timeout = ${?GATEWAY_STATUS_HEALTH_EXTERNAL_TIMEOUT}
|
||||||
|
}
|
||||||
|
|
||||||
|
cloud-events {
|
||||||
|
empty-schema-allowed = true
|
||||||
|
data-types = [
|
||||||
|
"application/json"
|
||||||
|
"application/vnd.eclipse.ditto+json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
cache {
|
||||||
|
publickeys {
|
||||||
|
maxentries = 32
|
||||||
|
expiry = 60m
|
||||||
|
maximum-size = ${ditto.gateway.cache.publickeys.maxentries}
|
||||||
|
expire-after-write = ${ditto.gateway.cache.publickeys.expiry}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statistics {
|
||||||
|
ask-timeout = 5s
|
||||||
|
ask-timeout = ${?STATISTICS_UPDATE_INTERVAL}
|
||||||
|
update-interval = 15s
|
||||||
|
update-interval = ${?STATISTICS_UPDATE_INTERVAL}
|
||||||
|
details-expire-after = 3s
|
||||||
|
details-expire-after = ${?STATISTICS_DETAILS_EXPIRE_AFTER}
|
||||||
|
shards = [
|
||||||
|
{
|
||||||
|
region = "thing"
|
||||||
|
role = "things"
|
||||||
|
root = "/user/thingsRoot"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
region = "policy"
|
||||||
|
role = "policies"
|
||||||
|
root = "/user/policiesRoot"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
region = "search-wildcard-updater"
|
||||||
|
role = "search"
|
||||||
|
root = "/user/thingsWildcardSearchRoot/searchUpdaterRoot"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing {
|
||||||
|
filter = {
|
||||||
|
includes = ["**"]
|
||||||
|
excludes = ["GET /ws/2"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets {
|
||||||
|
devops_password {
|
||||||
|
name = "devops_password"
|
||||||
|
name = ${?DEVOPS_PASSWORD_NAME}
|
||||||
|
}
|
||||||
|
status_password {
|
||||||
|
name = "status_password"
|
||||||
|
name = ${?STATUS_PASSWORD_NAME}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pekko.http.client {
|
||||||
|
user-agent-header = eclipse-ditto/${ditto.version}
|
||||||
|
}
|
||||||
|
|
||||||
|
pekko {
|
||||||
|
actor {
|
||||||
|
default-dispatcher {
|
||||||
|
executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedForkJoinExecutorServiceConfigurator"
|
||||||
|
}
|
||||||
|
deployment {
|
||||||
|
/gatewayRoot/proxy {
|
||||||
|
router = round-robin-pool
|
||||||
|
resizer {
|
||||||
|
lower-bound = 5
|
||||||
|
upper-bound = 100
|
||||||
|
messages-per-resize = 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cluster {
|
||||||
|
sharding {
|
||||||
|
role = ${ditto.service-name}
|
||||||
|
passivation {
|
||||||
|
strategy = "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roles = ["gateway"]
|
||||||
|
}
|
||||||
|
coordinated-shutdown {
|
||||||
|
phases {
|
||||||
|
service-requests-done {
|
||||||
|
timeout = 70s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
server-header = ""
|
||||||
|
request-timeout = ${ditto.gateway.http.request-timeout}
|
||||||
|
idle-timeout = 610s
|
||||||
|
max-connections = 4096
|
||||||
|
raw-request-uri-header = on
|
||||||
|
parsing {
|
||||||
|
max-uri-length = 8k
|
||||||
|
max-content-length = 1m
|
||||||
|
uri-parsing-mode = relaxed
|
||||||
|
}
|
||||||
|
websocket {
|
||||||
|
periodic-keep-alive-mode = ping
|
||||||
|
periodic-keep-alive-max-idle = 30s
|
||||||
|
}
|
||||||
|
termination-deadline-exceeded-response {
|
||||||
|
status = 502
|
||||||
|
}
|
||||||
|
}
|
||||||
|
host-connection-pool {
|
||||||
|
max-open-requests = 1024
|
||||||
|
idle-timeout = 60s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
management.health-checks.readiness-checks {
|
||||||
|
gateway-http-readiness = "org.eclipse.ditto.gateway.service.health.GatewayHttpReadinessCheck"
|
||||||
|
}
|
||||||
|
management.health-checks.liveness-checks {
|
||||||
|
subsystem-health = "org.eclipse.ditto.internal.utils.health.SubsystemHealthCheck"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authentication-dispatcher {
|
||||||
|
type = Dispatcher
|
||||||
|
executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
|
||||||
|
thread-pool-executor {
|
||||||
|
core-pool-size-min = 4
|
||||||
|
core-pool-size-factor = 2.0
|
||||||
|
core-pool-size-max = 8
|
||||||
|
}
|
||||||
|
throughput = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
signal-enrichment-cache-dispatcher {
|
||||||
|
type = Dispatcher
|
||||||
|
executor = "org.eclipse.ditto.internal.utils.metrics.service.executor.InstrumentedThreadPoolExecutorServiceConfigurator"
|
||||||
|
}
|
||||||
169
configuration/ditto/modify-jar.py
Normal file
169
configuration/ditto/modify-jar.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Modify the reference.conf inside the Ditto gateway JAR.
|
||||||
|
Inserts ditto { gateway { authentication { pre-authentication { enabled = true } } } }
|
||||||
|
at the root level, before the final closing brace.
|
||||||
|
"""
|
||||||
|
import zipfile
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
JAR_PATH = "/tmp/ditto-jar-mod/ditto-gateway-service-3.8.12-allinone.jar"
|
||||||
|
AUTH_BLOCK = """
|
||||||
|
### Custom Ditto auth override - pre-authentication enabled
|
||||||
|
ditto {
|
||||||
|
gateway {
|
||||||
|
authentication {
|
||||||
|
pre-authentication {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
devops {
|
||||||
|
secured = false
|
||||||
|
devops-authentication-method = "basic"
|
||||||
|
password = "ditto-devops-secret"
|
||||||
|
status-secured = false
|
||||||
|
status-authentication-method = "basic"
|
||||||
|
statusPassword = "ditto-status-secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def main():
|
||||||
|
os.makedirs("/tmp/ditto-jar-mod", exist_ok=True)
|
||||||
|
|
||||||
|
print("=== Step 1: Extracting JAR ===")
|
||||||
|
result = subprocess.run(
|
||||||
|
["docker", "run", "--rm", "eclipse/ditto-gateway:latest",
|
||||||
|
"cat", "/opt/ditto/ditto-gateway-service-3.8.12-allinone.jar"],
|
||||||
|
capture_output=True, check=True
|
||||||
|
)
|
||||||
|
with open(JAR_PATH, "wb") as f:
|
||||||
|
f.write(result.stdout)
|
||||||
|
print(f"JAR: {len(result.stdout)} bytes")
|
||||||
|
|
||||||
|
print("=== Step 2: Modifying reference.conf ===")
|
||||||
|
with zipfile.ZipFile(JAR_PATH, 'r') as zin:
|
||||||
|
ref_conf = zin.read("reference.conf").decode("utf-8")
|
||||||
|
|
||||||
|
lines = ref_conf.split('\n')
|
||||||
|
total_lines = len(lines)
|
||||||
|
print(f"reference.conf: {total_lines} lines")
|
||||||
|
|
||||||
|
# Find the LAST closing brace at root level (depth 0)
|
||||||
|
# Track depth through the entire file
|
||||||
|
depth = 0
|
||||||
|
last_root_close_idx = None
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped or stripped.startswith('#'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Count braces (simple approach - count { and })
|
||||||
|
# This isn't perfect for HOCON but works for our case
|
||||||
|
for ch in stripped:
|
||||||
|
if ch == '{':
|
||||||
|
depth += 1
|
||||||
|
elif ch == '}':
|
||||||
|
depth -= 1
|
||||||
|
|
||||||
|
if depth == 0:
|
||||||
|
last_root_close_idx = i
|
||||||
|
|
||||||
|
if last_root_close_idx is None:
|
||||||
|
print("ERROR: Could not find root-level closing brace!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
insert_idx = last_root_close_idx
|
||||||
|
print(f"Last root-level '}}' at line {insert_idx + 1}: '{lines[insert_idx].strip()}'")
|
||||||
|
|
||||||
|
# Check if we need a comma before our block
|
||||||
|
# Look at the non-empty line before insert_idx
|
||||||
|
prev_idx = insert_idx - 1
|
||||||
|
while prev_idx >= 0 and lines[prev_idx].strip() == '':
|
||||||
|
prev_idx -= 1
|
||||||
|
|
||||||
|
if prev_idx >= 0:
|
||||||
|
prev_stripped = lines[prev_idx].strip()
|
||||||
|
if prev_stripped.endswith('}'):
|
||||||
|
# Need to add a comma
|
||||||
|
lines[prev_idx] = lines[prev_idx].rstrip()
|
||||||
|
if not lines[prev_idx].endswith(','):
|
||||||
|
lines[prev_idx] += ','
|
||||||
|
print(f"Added comma to line {prev_idx + 1}")
|
||||||
|
|
||||||
|
# Insert our block
|
||||||
|
auth_lines = AUTH_BLOCK.split('\n')
|
||||||
|
new_lines = lines[:insert_idx] + auth_lines + lines[insert_idx:]
|
||||||
|
modified_conf = '\n'.join(new_lines)
|
||||||
|
|
||||||
|
print(f"Modified: {total_lines} -> {len(new_lines)} lines")
|
||||||
|
|
||||||
|
# Verify brace balance
|
||||||
|
depth = 0
|
||||||
|
for i, line in enumerate(new_lines):
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped or stripped.startswith('#'):
|
||||||
|
continue
|
||||||
|
for ch in stripped:
|
||||||
|
if ch == '{':
|
||||||
|
depth += 1
|
||||||
|
elif ch == '}':
|
||||||
|
depth -= 1
|
||||||
|
if depth < 0:
|
||||||
|
print(f"ERROR: Negative depth at line {i+1}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Brace depth check: {depth} (should be 0)")
|
||||||
|
if depth != 0:
|
||||||
|
print("ERROR: Unbalanced braces!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Verify ditto is at root level
|
||||||
|
for i, line in enumerate(new_lines):
|
||||||
|
if line.strip() == 'ditto {':
|
||||||
|
indent = len(line) - len(line.lstrip())
|
||||||
|
print(f"'ditto {{' at line {i+1}, indent: {indent}")
|
||||||
|
if indent != 0:
|
||||||
|
print(f"WARNING: Expected indent 0, got {indent}")
|
||||||
|
break
|
||||||
|
|
||||||
|
print("=== Step 3: Creating unsigned JAR ===")
|
||||||
|
skip_files = set()
|
||||||
|
with zipfile.ZipFile(JAR_PATH, 'r') as zin:
|
||||||
|
for name in zin.namelist():
|
||||||
|
if name.startswith("META-INF/"):
|
||||||
|
upper = name.upper()
|
||||||
|
if upper.endswith(".SF") or upper.endswith(".RSA") or upper.endswith(".DSA") or upper == "MANIFEST.MF":
|
||||||
|
skip_files.add(name)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(JAR_PATH + ".new", 'w', zipfile.ZIP_DEFLATED) as zout:
|
||||||
|
for item in zin.infolist():
|
||||||
|
if item.filename in skip_files:
|
||||||
|
continue
|
||||||
|
data = zin.read(item.filename)
|
||||||
|
if item.filename == "reference.conf":
|
||||||
|
data = modified_conf.encode("utf-8")
|
||||||
|
info = zipfile.ZipInfo(filename=item.filename, date_time=item.date_time)
|
||||||
|
info.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
info.external_attr = item.external_attr
|
||||||
|
zout.writestr(info, data)
|
||||||
|
|
||||||
|
shutil.move(JAR_PATH + ".new", JAR_PATH)
|
||||||
|
|
||||||
|
print("=== Step 4: Verifying ===")
|
||||||
|
with zipfile.ZipFile(JAR_PATH, 'r') as z:
|
||||||
|
ref = z.read("reference.conf").decode("utf-8")
|
||||||
|
assert "pre-authentication" in ref
|
||||||
|
sig = [n for n in z.namelist() if n.startswith("META-INF/") and n.upper().endswith(('.SF', '.RSA', '.DSA'))]
|
||||||
|
print(f"OK Signature files: {len(sig)}")
|
||||||
|
print(f"OK JAR size: {os.path.getsize(JAR_PATH)}")
|
||||||
|
|
||||||
|
print("\n=== DONE ===")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
70
configuration/ditto/oauth2-server.js
Normal file
70
configuration/ditto/oauth2-server.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Minimal JWT/OAuth2 server for Ditto
|
||||||
|
# Serves a JWKS endpoint and validates tokens signed with DITTO_JWT_SECRET
|
||||||
|
|
||||||
|
cat > /tmp/oauth2-server.js << 'EOF'
|
||||||
|
const http = require('http');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const SECRET = process.env.DITTO_JWT_SECRET || 'my-ditto-jwt-secret-key-12345';
|
||||||
|
const PORT = 3000;
|
||||||
|
|
||||||
|
function base64url(buf) {
|
||||||
|
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a token
|
||||||
|
function generateToken(sub, scope) {
|
||||||
|
const header = { alg: 'RS256', typ: 'JWT', kid: 'ditto-local' };
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const payload = {
|
||||||
|
iss: 'http://localhost:' + PORT,
|
||||||
|
sub: sub,
|
||||||
|
aud: 'ditto:cognito',
|
||||||
|
iat: now,
|
||||||
|
exp: now + 3600,
|
||||||
|
scope: scope
|
||||||
|
};
|
||||||
|
const h = base64url(Buffer.from(JSON.stringify(header)));
|
||||||
|
const p = base64url(Buffer.from(JSON.stringify(payload)));
|
||||||
|
const sig = base64url(
|
||||||
|
crypto.createHmac('sha256', SECRET).update(h + '.' + p).digest()
|
||||||
|
);
|
||||||
|
return h + '.' + p + '.' + sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
|
||||||
|
if (req.url === '/.well-known/openid-configuration') {
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
issuer: 'http://localhost:' + PORT,
|
||||||
|
jwks_uri: 'http://localhost:' + PORT + '/.well-known/jwks.json',
|
||||||
|
token_endpoint: 'http://localhost:' + PORT + '/token'
|
||||||
|
}));
|
||||||
|
} else if (req.url === '/.well-known/jwks.json') {
|
||||||
|
// Extract public key from secret (for HS256 we just return the secret as k)
|
||||||
|
const jwk = {
|
||||||
|
kty: 'oct',
|
||||||
|
kid: 'ditto-local',
|
||||||
|
use: 'sig',
|
||||||
|
alg: 'HS256',
|
||||||
|
k: base64url(Buffer.from(SECRET))
|
||||||
|
};
|
||||||
|
res.end(JSON.stringify({ keys: [jwk] }));
|
||||||
|
} else if (req.url === '/token') {
|
||||||
|
const token = generateToken('ditto', 'READ_WRITE');
|
||||||
|
res.end(JSON.stringify({ access_token: token, token_type: 'Bearer', expires_in: 3600 }));
|
||||||
|
} else {
|
||||||
|
res.statusCode = 404;
|
||||||
|
res.end('{}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, '0.0.0.0', () => {
|
||||||
|
console.log('OAuth2 server listening on port ' + PORT);
|
||||||
|
console.log('Token: ' + generateToken('ditto', scope='READ_WRITE'));
|
||||||
|
});
|
||||||
|
EOF
|
||||||
|
|
||||||
|
node /tmp/oauth2-server.js
|
||||||
14
configuration/ditto/override.conf
Normal file
14
configuration/ditto/override.conf
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
ditto {
|
||||||
|
gateway {
|
||||||
|
authentication {
|
||||||
|
pre-authentication {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
devops {
|
||||||
|
secured = false
|
||||||
|
devops-authentication-method = "basic"
|
||||||
|
password = "ditto-devops-secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
ditto-things.yml
Normal file
25
ditto-things.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
services:
|
||||||
|
ditto-things:
|
||||||
|
image: eclipse/ditto-things:latest
|
||||||
|
container_name: smart-city-ditto-things
|
||||||
|
restart: unless-stopped
|
||||||
|
hostname: ditto-things
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Berlin
|
||||||
|
- BIND_HOSTNAME=0.0.0.0
|
||||||
|
- DITTO_JWT_SECRET=my-ditto-jwt-secret-key-12345
|
||||||
|
- MONGO_HOST=smart-city-ditto-mongodb
|
||||||
|
- MONGO_PORT=27017
|
||||||
|
- MONGO_DB=Things
|
||||||
|
- MONGODB_URI=mongodb://smart-city-ditto-mongodb:27017/Things
|
||||||
|
- AKKA_REMOTE_ENABLED=false
|
||||||
|
- JAVA_TOOL_OPTIONS=-Dditto.things.authentication.devops.password=ditto-devops-secret
|
||||||
|
networks:
|
||||||
|
traefik-public:
|
||||||
|
aliases:
|
||||||
|
- ditto-cluster
|
||||||
|
- ditto-things
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik-public:
|
||||||
|
external: true
|
||||||
@@ -68,7 +68,7 @@ services:
|
|||||||
- "traefik.http.services.ditto-things.loadbalancer.server.port=8080"
|
- "traefik.http.services.ditto-things.loadbalancer.server.port=8080"
|
||||||
|
|
||||||
ditto-gateway:
|
ditto-gateway:
|
||||||
image: eclipse/ditto-gateway:latest
|
image: eclipse/ditto-gateway:custom
|
||||||
container_name: smart-city-ditto-gateway
|
container_name: smart-city-ditto-gateway
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
hostname: ditto-gateway
|
hostname: ditto-gateway
|
||||||
@@ -85,19 +85,11 @@ services:
|
|||||||
- DITTO_GW_MQTT_BROKER=smart-city-mosquitto:1883
|
- DITTO_GW_MQTT_BROKER=smart-city-mosquitto:1883
|
||||||
- DITTO_GW_MQTT_TOPIC_FILTER=smartcity/#
|
- DITTO_GW_MQTT_TOPIC_FILTER=smartcity/#
|
||||||
- DEVOPS_PASSWORD=ditto-devops-secret
|
- DEVOPS_PASSWORD=ditto-devops-secret
|
||||||
- ENABLE_PRE_AUTHENTICATION=true
|
|
||||||
- JAVA_TOOL_OPTIONS=-Dditto.gateway.authentication.devops.password=ditto-devops-secret -Dditto.gateway.authentication.devops.secured=true -Dditto.gateway.authentication.devops.devops-authentication-method=basic
|
|
||||||
networks:
|
networks:
|
||||||
traefik-public:
|
traefik-public:
|
||||||
aliases:
|
aliases:
|
||||||
- ditto-cluster
|
- ditto-cluster
|
||||||
- ditto-gateway
|
- ditto-gateway
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.ditto-gateway.rule=Host(`ditto.digitribe.fr`)"
|
|
||||||
- "traefik.http.routers.ditto-gateway.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.ditto-gateway.tls.certresolver=letsencrypt"
|
|
||||||
- "traefik.http.services.ditto-gateway.loadbalancer.server.port=8080"
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
traefik-public:
|
traefik-public:
|
||||||
|
|||||||
48
flink/docker-compose.yml
Normal file
48
flink/docker-compose.yml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Smart City Digital Twin Martinique — Apache Flink
|
||||||
|
# Usage: docker compose -f flink/docker-compose.yml up -d
|
||||||
|
# Image officielle Apache Flink 1.20.1 avec digest vérifié
|
||||||
|
|
||||||
|
networks:
|
||||||
|
smartcity-shared:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
jobmanager:
|
||||||
|
image: apache/flink:1.20.1-scala_2.12-java17@sha256:ecc5785594eff2d94e29e6b116b3124c0cdb3a9c952ebdf38ef0fef90fb9913d
|
||||||
|
container_name: flink-jobmanager
|
||||||
|
command: jobmanager
|
||||||
|
networks:
|
||||||
|
- smartcity-shared
|
||||||
|
ports:
|
||||||
|
- "8081:8081" # Flink Web UI
|
||||||
|
environment:
|
||||||
|
- |
|
||||||
|
FLINK_PROPERTIES=
|
||||||
|
jobmanager.rpc.address: jobmanager
|
||||||
|
jobmanager.memory.process.size: 1024m
|
||||||
|
taskmanager.memory.process.size: 1024m
|
||||||
|
taskmanager.numberOfTaskSlots: 4
|
||||||
|
parallelism.default: 2
|
||||||
|
rest.port: 8081
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=false"
|
||||||
|
|
||||||
|
taskmanager:
|
||||||
|
image: apache/flink:1.20.1-scala_2.12-java17@sha256:ecc5785594eff2d94e29e6b116b3124c0cdb3a9c952ebdf38ef0fef90fb9913d
|
||||||
|
container_name: flink-taskmanager
|
||||||
|
command: taskmanager
|
||||||
|
networks:
|
||||||
|
- smartcity-shared
|
||||||
|
depends_on:
|
||||||
|
- jobmanager
|
||||||
|
environment:
|
||||||
|
- |
|
||||||
|
FLINK_PROPERTIES=
|
||||||
|
jobmanager.rpc.address: jobmanager
|
||||||
|
taskmanager.memory.process.size: 1024m
|
||||||
|
taskmanager.numberOfTaskSlots: 4
|
||||||
|
parallelism.default: 2
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=false"
|
||||||
@@ -3,7 +3,7 @@ FROM jupyterhub/jupyterhub:5.3.0
|
|||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y --no-install-recommends git sudo && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN pip install --no-cache-dir \
|
RUN pip install --no-cache-dir \
|
||||||
git+https://github.com/jupyterhub/nativeauthenticator.git@main \
|
git+https://github.com/jupyterhub/nativeauthenticator.git@main \
|
||||||
@@ -12,9 +12,17 @@ RUN pip install --no-cache-dir \
|
|||||||
jupyterlab \
|
jupyterlab \
|
||||||
notebook
|
notebook
|
||||||
|
|
||||||
|
# Create eric user with password-less sudo
|
||||||
|
RUN useradd -m -s /bin/bash -u 1000 eric && \
|
||||||
|
echo "eric ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
|
||||||
|
chown -R eric:eric /home/eric
|
||||||
|
|
||||||
# Create the directory for JupyterHub data
|
# Create the directory for JupyterHub data
|
||||||
RUN mkdir -p /srv/jupyterhub && chown -R 1000:1000 /srv/jupyterhub
|
RUN mkdir -p /srv/jupyterhub && chown -R 1000:1000 /srv/jupyterhub
|
||||||
|
|
||||||
|
ARG BUILD_DATE=unknown
|
||||||
|
RUN echo "Build date: ${BUILD_DATE}" > /tmp/build-info.txt
|
||||||
|
|
||||||
COPY jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py
|
COPY jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py
|
||||||
|
|
||||||
WORKDIR /srv/jupyterhub
|
WORKDIR /srv/jupyterhub
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
# JupyterHub configuration for Smart City VRE
|
# JupyterHub configuration for Smart City VRE
|
||||||
|
# Uses NativeAuthenticator with LocalProcessSpawner
|
||||||
|
# Build: 2026-06-01-0915
|
||||||
|
|
||||||
|
import sys as _sys
|
||||||
|
|
||||||
c.JupyterHub.ip = '0.0.0.0'
|
c.JupyterHub.ip = '0.0.0.0'
|
||||||
c.JupyterHub.port = 8000
|
c.JupyterHub.port = 8000
|
||||||
@@ -9,14 +13,14 @@ c.JupyterHub.authenticator_class = 'nativeauthenticator.NativeAuthenticator'
|
|||||||
c.Authenticator.admin_users = {'admin'}
|
c.Authenticator.admin_users = {'admin'}
|
||||||
c.Authenticator.allow_all = True
|
c.Authenticator.allow_all = True
|
||||||
|
|
||||||
# Spawner — use SimpleLocalProcessSpawner (default in JupyterHub 5.x)
|
# Spawner: Simple local spawner (single-node, shared environment)
|
||||||
# This spawner runs singleuser servers as subprocesses
|
c.JupyterHub.spawner_class = 'simple'
|
||||||
c.JupyterHub.spawner_class = 'jupyterhub.spawner.SimpleLocalProcessSpawner'
|
c.Spawner.cmd = ['jupyterhub-singleuser']
|
||||||
c.Spawner.default_url = '/lab'
|
c.Spawner.default_url = '/lab'
|
||||||
c.Spawner.http_timeout = 300
|
|
||||||
c.Spawner.start_timeout = 300
|
c.Spawner.start_timeout = 300
|
||||||
|
c.Spawner.http_timeout = 120
|
||||||
|
|
||||||
# Database and cookies - use absolute paths
|
# Database and cookies - use absolute path (4 slashes!)
|
||||||
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'
|
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'
|
||||||
c.JupyterHub.db_url = 'sqlite:////srv/jupyterhub/jupyterhub.sqlite'
|
c.JupyterHub.db_url = 'sqlite:////srv/jupyterhub/jupyterhub.sqlite'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user