fix: stabilize im server startup and p2p topic scans
This commit is contained in:
@@ -41,7 +41,7 @@ ENV RESET_DB=false
|
|||||||
ENV UPGRADE_DB=false
|
ENV UPGRADE_DB=false
|
||||||
|
|
||||||
# Option to skip DB initialization when it's missing.
|
# Option to skip DB initialization when it's missing.
|
||||||
ENV NO_DB_INIT=false
|
ENV NO_DB_INIT=true
|
||||||
|
|
||||||
# Load sample data to database from data.json.
|
# Load sample data to database from data.json.
|
||||||
ARG SAMPLE_DATA=data.json
|
ARG SAMPLE_DATA=data.json
|
||||||
@@ -130,7 +130,7 @@ ENV TNPG_ORG=
|
|||||||
|
|
||||||
# Video calls configuration.
|
# Video calls configuration.
|
||||||
ENV WEBRTC_ENABLED=false
|
ENV WEBRTC_ENABLED=false
|
||||||
ENV ICE_SERVERS_FILE=
|
ENV ICE_SERVERS_FILE=/opt/tinode/ice-servers.json
|
||||||
|
|
||||||
# Use the target db by default.
|
# Use the target db by default.
|
||||||
# When TARGET_DB is "alldbs", it is the user's responsibility
|
# When TARGET_DB is "alldbs", it is the user's responsibility
|
||||||
@@ -153,6 +153,7 @@ WORKDIR /opt/tinode
|
|||||||
# Copy config template to the container.
|
# Copy config template to the container.
|
||||||
COPY config.template .
|
COPY config.template .
|
||||||
COPY entrypoint.sh .
|
COPY entrypoint.sh .
|
||||||
|
COPY ice-servers.json .
|
||||||
|
|
||||||
# Get the desired Tinode build.
|
# Get the desired Tinode build.
|
||||||
ADD https://github.com/tinode/chat/releases/download/v$BINVERS/tinode-$TARGET_DB.linux-amd64.tar.gz .
|
ADD https://github.com/tinode/chat/releases/download/v$BINVERS/tinode-$TARGET_DB.linux-amd64.tar.gz .
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ LABEL name="ImServer"
|
|||||||
ENV WAIT_FOR=
|
ENV WAIT_FOR=
|
||||||
ENV RESET_DB=false
|
ENV RESET_DB=false
|
||||||
ENV UPGRADE_DB=false
|
ENV UPGRADE_DB=false
|
||||||
ENV NO_DB_INIT=false
|
ENV NO_DB_INIT=true
|
||||||
# Empty by default: do not load Tinode's canned demo users into a real deployment.
|
# Empty by default: do not load Tinode's canned demo users into a real deployment.
|
||||||
# Override explicitly if sample data is ever wanted.
|
# Override explicitly if sample data is ever wanted.
|
||||||
ENV SAMPLE_DATA=
|
ENV SAMPLE_DATA=
|
||||||
@@ -51,6 +51,7 @@ ENV FCM_PUSH_ENABLED=false
|
|||||||
ENV FCM_INCLUDE_ANDROID_NOTIFICATION=true
|
ENV FCM_INCLUDE_ANDROID_NOTIFICATION=true
|
||||||
ENV TNPG_PUSH_ENABLED=false
|
ENV TNPG_PUSH_ENABLED=false
|
||||||
ENV WEBRTC_ENABLED=false
|
ENV WEBRTC_ENABLED=false
|
||||||
|
ENV ICE_SERVERS_FILE=/opt/tinode/ice-servers.json
|
||||||
ENV SERVER_STATUS_PATH=''
|
ENV SERVER_STATUS_PATH=''
|
||||||
ENV ACC_GC_ENABLED=false
|
ENV ACC_GC_ENABLED=false
|
||||||
ENV PLUGIN_PYTHON_CHAT_BOT_ENABLED=false
|
ENV PLUGIN_PYTHON_CHAT_BOT_ENABLED=false
|
||||||
@@ -68,6 +69,7 @@ WORKDIR /opt/tinode
|
|||||||
|
|
||||||
COPY docker/tinode/config.template .
|
COPY docker/tinode/config.template .
|
||||||
COPY docker/tinode/entrypoint.sh .
|
COPY docker/tinode/entrypoint.sh .
|
||||||
|
COPY docker/tinode/ice-servers.json .
|
||||||
COPY tinode-db/credentials.sh .
|
COPY tinode-db/credentials.sh .
|
||||||
COPY server/templ ./templ
|
COPY server/templ ./templ
|
||||||
COPY --from=build /out/tinode .
|
COPY --from=build /out/tinode .
|
||||||
|
|||||||
+52
-20
@@ -113,27 +113,59 @@ if [ ! -z "$WAIT_FOR" ] ; then
|
|||||||
until nc -z -v -w5 ${DB[0]} ${DB[1]}; do echo "waiting for ${WAIT_FOR}..."; sleep 3; done
|
until nc -z -v -w5 ${DB[0]} ${DB[1]}; do echo "waiting for ${WAIT_FOR}..."; sleep 3; done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Initialize the database if it has not been initialized yet or if data reset/upgrade has been requested.
|
run_with_retries() {
|
||||||
init_stdout=./init-db-stdout.txt
|
local name="$1"
|
||||||
./init-db \
|
shift
|
||||||
--reset=${RESET_DB} \
|
local attempts="${STARTUP_RETRY_ATTEMPTS:-10}"
|
||||||
--upgrade=${UPGRADE_DB} \
|
local delay="${STARTUP_RETRY_DELAY_SECONDS:-3}"
|
||||||
--config=${CONFIG} \
|
local i=1
|
||||||
--data=${SAMPLE_DATA} \
|
while [ "$i" -le "$attempts" ]; do
|
||||||
--no_init=${NO_DB_INIT} \
|
"$@"
|
||||||
1>${init_stdout}
|
local status=$?
|
||||||
if [ $? -ne 0 ]; then
|
if [ "$status" -eq 0 ]; then
|
||||||
echo "./init-db failed. Quitting."
|
return 0
|
||||||
exit 1
|
fi
|
||||||
fi
|
echo "$name failed with status $status (attempt $i/$attempts)."
|
||||||
|
if [ "$i" -eq "$attempts" ]; then
|
||||||
|
return "$status"
|
||||||
|
fi
|
||||||
|
sleep "$delay"
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
# Create/upgrade the hotgo tenant registry and the workbench/org/meetings/
|
init_db_once() {
|
||||||
# reports/mailbox/etc. tables that init-db above does not manage. Idempotent
|
./init-db \
|
||||||
# (IF NOT EXISTS / ON DUPLICATE KEY UPDATE throughout), safe to run every start.
|
--reset=${RESET_DB} \
|
||||||
IM_MYSQL_DSN="${MYSQL_DSN}" ./apply-hotgo-tenant-db
|
--upgrade=${UPGRADE_DB} \
|
||||||
if [ $? -ne 0 ]; then
|
--config=${CONFIG} \
|
||||||
echo "./apply-hotgo-tenant-db failed. Quitting."
|
--data=${SAMPLE_DATA} \
|
||||||
exit 1
|
--no_init=${NO_DB_INIT} \
|
||||||
|
1>${init_stdout}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_hotgo_tenant_db_once() {
|
||||||
|
IM_MYSQL_DSN="${MYSQL_DSN}" ./apply-hotgo-tenant-db
|
||||||
|
}
|
||||||
|
|
||||||
|
init_stdout=./init-db-stdout.txt
|
||||||
|
if [ "$NO_DB_INIT" = "true" ] ; then
|
||||||
|
echo "NO_DB_INIT=true: skipping init-db and apply-hotgo-tenant-db."
|
||||||
|
else
|
||||||
|
# Initialize the database if it has not been initialized yet or if data reset/upgrade has been requested.
|
||||||
|
run_with_retries "init-db" init_db_once
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "./init-db failed. Quitting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create/upgrade the hotgo tenant registry and the workbench/org/meetings/
|
||||||
|
# reports/mailbox/etc. tables that init-db above does not manage.
|
||||||
|
run_with_retries "apply-hotgo-tenant-db" apply_hotgo_tenant_db_once
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "./apply-hotgo-tenant-db failed. Quitting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If sample data was provided, try to find Tino password.
|
# If sample data was provided, try to find Tino password.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"urls": ["stun:stun.l.google.com:19302"]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -996,6 +996,10 @@ func (a *adapter) UpgradeDb() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := a.ensureMessageTopicSeqIndex(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if a.version == adpVersion {
|
if a.version == adpVersion {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1004,6 +1008,22 @@ func (a *adapter) UpgradeDb() error {
|
|||||||
strconv.Itoa(a.version) + " to " + strconv.Itoa(adpVersion) + " is not supported")
|
strconv.Itoa(a.version) + " to " + strconv.Itoa(adpVersion) + " is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *adapter) ensureMessageTopicSeqIndex() error {
|
||||||
|
const indexName = "messages_topic_seqid"
|
||||||
|
var count int
|
||||||
|
if err := a.db.QueryRow(
|
||||||
|
`SELECT COUNT(*) FROM information_schema.statistics
|
||||||
|
WHERE table_schema=DATABASE() AND table_name='im_messages' AND index_name=?`,
|
||||||
|
indexName).Scan(&count); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := a.db.Exec("ALTER TABLE im_messages ADD UNIQUE INDEX messages_topic_seqid(topic, seqid)")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func createIMNotificationTables(tx *sql.Tx) error {
|
func createIMNotificationTables(tx *sql.Tx) error {
|
||||||
_, err := tx.Exec(
|
_, err := tx.Exec(
|
||||||
`CREATE TABLE im_system_notifications(
|
`CREATE TABLE im_system_notifications(
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDefaultAccessScanAllowsNil(t *testing.T) {
|
||||||
|
var da DefaultAccess
|
||||||
|
if err := da.Scan(nil); err != nil {
|
||||||
|
t.Fatalf("Scan(nil) returned error: %v", err)
|
||||||
|
}
|
||||||
|
if da.Auth != ModeNone || da.Anon != ModeNone {
|
||||||
|
t.Fatalf("Scan(nil) changed default access: %#v", da)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAccessScanJSON(t *testing.T) {
|
||||||
|
for name, input := range map[string]any{
|
||||||
|
"bytes": []byte(`{"Auth":"JRWP","Anon":"N"}`),
|
||||||
|
"string": `{"Auth":"JRWP","Anon":"N"}`,
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var da DefaultAccess
|
||||||
|
if err := da.Scan(input); err != nil {
|
||||||
|
t.Fatalf("Scan returned error: %v", err)
|
||||||
|
}
|
||||||
|
if da.Auth.String() != "JRWP" || da.Anon.String() != "N" {
|
||||||
|
t.Fatalf("unexpected default access: %#v", da)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -863,7 +863,17 @@ type DefaultAccess struct {
|
|||||||
// Scan is an implementation of Scanner interface so the value can be read from SQL DBs
|
// Scan is an implementation of Scanner interface so the value can be read from SQL DBs
|
||||||
// It assumes the value is serialized and stored as JSON
|
// It assumes the value is serialized and stored as JSON
|
||||||
func (da *DefaultAccess) Scan(val any) error {
|
func (da *DefaultAccess) Scan(val any) error {
|
||||||
return json.Unmarshal(val.([]byte), da)
|
if val == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch v := val.(type) {
|
||||||
|
case []byte:
|
||||||
|
return json.Unmarshal(v, da)
|
||||||
|
case string:
|
||||||
|
return json.Unmarshal([]byte(v), da)
|
||||||
|
default:
|
||||||
|
return errors.New("scan failed: data is not a byte slice or string")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value implements sql's driver.Valuer interface.
|
// Value implements sql's driver.Valuer interface.
|
||||||
|
|||||||
@@ -142,12 +142,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"webrtc": {
|
|
||||||
"enabled": false,
|
|
||||||
"call_establishment_timeout": 30,
|
|
||||||
"ice_servers": []
|
|
||||||
},
|
|
||||||
|
|
||||||
"cluster_config": {
|
"cluster_config": {
|
||||||
"self": "",
|
"self": "",
|
||||||
"nodes": [],
|
"nodes": [],
|
||||||
|
|||||||
Reference in New Issue
Block a user