File size: 12,263 Bytes
a8673b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#!/bin/sh
set -e

CONFIG_FILE="${APP_HOME}/config.yaml"

# Priority 1: Use USERNAME/PASSWORD if both are provided
if [ -n "${USERNAME}" ] && [ -n "${PASSWORD}" ]; then
  echo "--- Basic auth enabled: Creating config.yaml with provided credentials. ---"
  
  cat <<EOT > ${CONFIG_FILE}

dataRoot: ./data

listen: true

listenAddress:

  ipv4: 0.0.0.0

  ipv6: '[::]'

protocol:

    ipv4: true

    ipv6: false

dnsPreferIPv6: false

autorunHostname: "auto"

port: 8000

autorunPortOverride: -1

ssl:

  enabled: false

  certPath: "./certs/cert.pem"

  keyPath: "./certs/privkey.pem"

whitelistMode: false

enableForwardedWhitelist: false

whitelist:

  - ::1

  - 127.0.0.1

whitelistDockerHosts: true

basicAuthMode: true

basicAuthUser:

  username: "${USERNAME}"

  password: "${PASSWORD}"

enableCorsProxy: false

requestProxy:

  enabled: false

  url: "socks5://username:password@example.com:1080"

  bypass:

    - localhost

    - 127.0.0.1

enableUserAccounts: false

enableDiscreetLogin: false

autheliaAuth: false

perUserBasicAuth: false

sessionTimeout: -1

disableCsrfProtection: false

securityOverride: false

logging:

  enableAccessLog: true

  minLogLevel: 0

rateLimiting:

  preferRealIpHeader: false

autorun: false

avoidLocalhost: false

backups:

  common:

    numberOfBackups: 50

  chat:

    enabled: true

    checkIntegrity: true

    maxTotalBackups: -1

    throttleInterval: 10000

thumbnails:

  enabled: true

  format: "jpg"

  quality: 95

  dimensions: { 'bg': [160, 90], 'avatar': [96, 144] }

performance:

  lazyLoadCharacters: false

  memoryCacheCapacity: '100mb'

  useDiskCache: true

allowKeysExposure: true

skipContentCheck: false

whitelistImportDomains:

  - localhost

  - cdn.discordapp.com

  - files.catbox.moe

  - raw.githubusercontent.com

requestOverrides: []

extensions:

  enabled: true

  autoUpdate: false

  models:

    autoDownload: true

    classification: Cohee/distilbert-base-uncased-go-emotions-onnx

    captioning: Xenova/vit-gpt2-image-captioning

    embedding: Cohee/jina-embeddings-v2-base-en

    speechToText: Xenova/whisper-small

    textToSpeech: Xenova/speecht5_tts

enableDownloadableTokenizers: true

promptPlaceholder: "[Start a new chat]"

openai:

  randomizeUserId: false

  captionSystemPrompt: ""

deepl:

  formality: default

mistral:

  enablePrefix: false

ollama:

  keepAlive: -1

  batchSize: -1

claude:

  enableSystemPromptCache: false

  cachingAtDepth: -1

enableServerPlugins: true

enableServerPluginsAutoUpdate: false

EOT

# Priority 2: Use CONFIG_YAML if provided (and username/password are not)
elif [ -n "${CONFIG_YAML}" ]; then
  echo "--- Found CONFIG_YAML, creating config.yaml from environment variable. ---"
  printf '%s\n' "${CONFIG_YAML}" > ${CONFIG_FILE}

# Priority 3: No config provided, let the app use its defaults
else
    echo "--- No user/pass or CONFIG_YAML provided. App will use its default settings. ---"
fi

# --- BEGIN: Update SillyTavern Core at Runtime ---
echo '--- Attempting to update SillyTavern Core from GitHub (staging branch) ---'
if [ -d ".git" ] && [ "$(git rev-parse --abbrev-ref HEAD)" = "staging" ]; then
  echo 'Existing staging branch found. Resetting and pulling latest changes...'
  git reset --hard HEAD && \
  git pull origin staging || echo 'WARN: git pull failed, continuing with code from build time.'
  echo '--- SillyTavern Core update check finished. ---'
else
  echo 'WARN: .git directory not found or not on staging branch. Skipping runtime update. Code from build time will be used.'
fi
# --- END: Update SillyTavern Core at Runtime ---

# --- BEGIN: Configure Git default identity at Runtime ---
echo '--- Configuring Git default user identity at runtime ---'
git config --global user.name "SillyTavern Sync" && \
git config --global user.email "sillytavern-sync@example.com" && \
git config --global --add safe.directory "${APP_HOME}/data"
echo '--- Git identity configured for runtime user. ---'
# --- END: Configure Git default identity at Runtime ---

# --- BEGIN: Dynamically Install Plugins at Runtime ---
echo '--- Checking for PLUGINS environment variable ---'
if [ -n "$PLUGINS" ]; then
  echo "*** Installing Plugins specified in PLUGINS environment variable: $PLUGINS ***"
  # Ensure plugins directory exists
  mkdir -p ./plugins && chown node:node ./plugins
  # Set comma as delimiter
  IFS=','
  # Loop through each plugin URL
  for plugin_url in $PLUGINS; do
    # Trim leading/trailing whitespace
    plugin_url=$(echo "$plugin_url" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
    if [ -z "$plugin_url" ]; then continue; fi
    # Extract plugin name
    plugin_name_git=$(basename "$plugin_url")
    plugin_name=${plugin_name_git%.git}
    plugin_dir="./plugins/$plugin_name"
    echo "--- Installing plugin: $plugin_name from $plugin_url into $plugin_dir ---"
    # Remove existing dir if it exists
    rm -rf "$plugin_dir"
    # Clone the plugin (run as root, fix perms later)
    git clone --depth 1 "$plugin_url" "$plugin_dir"
    if [ -f "$plugin_dir/package.json" ]; then
      echo "--- Installing dependencies for $plugin_name ---"
      (cd "$plugin_dir" && npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev --force && npm cache clean --force) || echo "WARN: Failed to install dependencies for $plugin_name"
    else
       echo "--- No package.json found for $plugin_name, skipping dependency install. ---"
    fi || echo "WARN: Failed to clone $plugin_name from $plugin_url, skipping..."
    
    # Configure cloud-saves plugin if this is the cloud-saves plugin
    if [ "$plugin_name" = "cloud-saves" ]; then
      echo "--- Detected cloud-saves plugin, checking for configuration environment variables ---"
      
      # Set default values
      REPO_URL_VALUE=${REPO_URL:-"https://github.com/fuwei99/sillytravern"}
      GITHUB_TOKEN_VALUE=${GITHUB_TOKEN:-""}
      AUTOSAVE_INTERVAL_VALUE=${AUTOSAVE_INTERVAL:-30}
      AUTOSAVE_TARGET_TAG_VALUE=${AUTOSAVE_TARGET_TAG:-""}
      
      # Always set autosave to false as required
      AUTOSAVE_ENABLED="false"
      
      echo "--- Creating cloud-saves plugin configuration file ---"
      CONFIG_JSON_FILE="$plugin_dir/config.json"
      
      # Generate config.json file
      cat <<EOT > ${CONFIG_JSON_FILE}

{

  "repo_url": "${REPO_URL_VALUE}",

  "branch": "main",

  "username": "cloud-saves",

  "github_token": "${GITHUB_TOKEN_VALUE}",

  "display_name": "",

  "is_authorized": true,

  "last_save": null,

  "current_save": null,

  "has_temp_stash": false,

  "autoSaveEnabled": ${AUTOSAVE_ENABLED},

  "autoSaveInterval": ${AUTOSAVE_INTERVAL_VALUE},

  "autoSaveTargetTag": "${AUTOSAVE_TARGET_TAG_VALUE}"

}

EOT
      
      # Set correct permissions for config file
      chown node:node ${CONFIG_JSON_FILE}
      
      echo "--- cloud-saves plugin configuration file created at: ${CONFIG_JSON_FILE} ---"
    fi
  done
  # Reset IFS
  unset IFS
  # Fix permissions for plugins directory after installation
  echo "--- Setting permissions for plugins directory ---"
  chown -R node:node ./plugins
  echo "*** Plugin installation finished. ***"
else
  echo 'PLUGINS environment variable is not set or empty, skipping runtime plugin installation.'
fi
# --- END: Dynamically Install Plugins at Runtime ---

echo "*** Starting SillyTavern... ***"
node ${APP_HOME}/server.js &
SERVER_PID=$!

echo "SillyTavern server started with PID ${SERVER_PID}. Waiting for it to become responsive..."

# --- Health Check Logic ---
HEALTH_CHECK_URL="http://localhost:8000/"
CURL_COMMAND="curl -sf"

# If basic auth is enabled, provide credentials to curl for health checks
if [ -n "${USERNAME}" ] && [ -n "${PASSWORD}" ]; then
    echo "--- Health check will use basic auth credentials. ---"
    # The -u flag provides user:password for basic auth
    CURL_COMMAND="curl -sf -u \"${USERNAME}:${PASSWORD}\""
fi

# Health check loop
RETRY_COUNT=0
MAX_RETRIES=12 # Wait for 60 seconds max
# Use eval to correctly execute the command string with quotes
while ! eval "${CURL_COMMAND} ${HEALTH_CHECK_URL}" > /dev/null; do
    RETRY_COUNT=$((RETRY_COUNT+1))
    if [ ${RETRY_COUNT} -ge ${MAX_RETRIES} ]; then
        echo "SillyTavern failed to start. Exiting."
        kill ${SERVER_PID}
        exit 1
    fi
    echo "SillyTavern is still starting or not responsive on port 8000, waiting 5 seconds..."
    sleep 5
done

echo "SillyTavern started successfully! Beginning periodic keep-alive..."

# --- BEGIN: Install Extensions after SillyTavern startup ---
install_extensions() {
    echo "--- Waiting 40 seconds before installing extensions... ---"
    sleep 40
    
    echo "--- Checking for EXTENSIONS environment variable ---"
    if [ -n "$EXTENSIONS" ]; then
        echo "*** Installing Extensions specified in EXTENSIONS environment variable: $EXTENSIONS ***"
        
        # Determine installation directory based on INSTALL_FOR_ALL_USERS
        if [ "$INSTALL_FOR_ALL_USERS" = "true" ]; then
            # System-level installation (for all users)
            EXTENSIONS_DIR="./public/scripts/extensions/third-party"
            echo "Installing extensions for all users in: $EXTENSIONS_DIR"
        else
            # User-level installation (for default user only)
            EXTENSIONS_DIR="./data/default-user/extensions"
            echo "Installing extensions for default user in: $EXTENSIONS_DIR"
        fi
        
        # Ensure extensions directory exists
        mkdir -p "$EXTENSIONS_DIR" && chown node:node "$EXTENSIONS_DIR"
        
        # Set comma as delimiter
        IFS=','
        
        # Loop through each extension URL
        for extension_url in $EXTENSIONS; do
            # Trim leading/trailing whitespace
            extension_url=$(echo "$extension_url" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            if [ -z "$extension_url" ]; then continue; fi
            
            # Extract extension name
            extension_name_git=$(basename "$extension_url")
            extension_name=${extension_name_git%.git}
            extension_dir="$EXTENSIONS_DIR/$extension_name"
            
            echo "--- Installing extension: $extension_name from $extension_url into $extension_dir ---"
            
            # Remove existing dir if it exists
            rm -rf "$extension_dir"
            
            # Clone the extension
            git clone --depth 1 "$extension_url" "$extension_dir"
            
            # Check if extension has package.json and install dependencies if needed
            if [ -f "$extension_dir/package.json" ]; then
                echo "--- Installing dependencies for $extension_name ---"
                (cd "$extension_dir" && npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev --force && npm cache clean --force) || echo "WARN: Failed to install dependencies for $extension_name"
            else
                echo "--- No package.json found for $extension_name, skipping dependency install. ---"
            fi || echo "WARN: Failed to clone $extension_name from $extension_url, skipping..."
        done
        
        # Reset IFS
        unset IFS
        
        # Fix permissions for extensions directory after installation
        echo "--- Setting permissions for extensions directory ---"
        chown -R node:node "$EXTENSIONS_DIR"
        
        echo "*** Extensions installation finished. ***"
    else
        echo 'EXTENSIONS environment variable is not set or empty, skipping extensions installation.'
    fi
}

# Run the extension installation in the background
install_extensions &
# --- END: Install Extensions after SillyTavern startup ---

# Keep-alive loop
while kill -0 ${SERVER_PID} 2>/dev/null; do
    echo "Sending keep-alive request to ${HEALTH_CHECK_URL}"
    # Use eval here as well for the keep-alive command
    eval "${CURL_COMMAND} ${HEALTH_CHECK_URL}" > /dev/null || echo "Keep-alive request failed."
    echo "Keep-alive request sent. Sleeping for 30 minutes."
    sleep 1800
done &

wait ${SERVER_PID}