Перевести deploy и PostgreSQL схемы на актуальное состояние

This commit is contained in:
AidarKC
2026-07-28 15:32:43 +04:00
parent d2ff277b16
commit 578f5cad4a
18 changed files with 699 additions and 46 deletions
@@ -18,7 +18,9 @@ public final class DatabaseInitializer {
public static final String DB_SCHEMA_VERSION_TABLE = "db_schema_version"; public static final String DB_SCHEMA_VERSION_TABLE = "db_schema_version";
public static final int SCHEMA_VERSION_1 = 1; public static final int SCHEMA_VERSION_1 = 1;
public static final int SCHEMA_VERSION_2 = 2;
public static final String POSTGRES_SCHEMA_RESOURCE = "postgres/schema_v1.sql"; public static final String POSTGRES_SCHEMA_RESOURCE = "postgres/schema_v1.sql";
public static final String POSTGRES_MIGRATION_V2_RESOURCE = "postgres/migration_v2.sql";
private DatabaseInitializer() {} private DatabaseInitializer() {}
@@ -73,10 +75,14 @@ public final class DatabaseInitializer {
} }
try (Connection conn = openConnection(jdbcUrl, user, password)) { try (Connection conn = openConnection(jdbcUrl, user, password)) {
if (postgresSchemaVersionTableExists(conn)) { if (!postgresSchemaVersionTableExists(conn)) {
runSqlScript(conn, POSTGRES_SCHEMA_RESOURCE);
return; return;
} }
runPostgresSchemaScript(conn); int currentVersion = readCurrentSchemaVersion(conn);
if (currentVersion < SCHEMA_VERSION_2) {
runSqlScript(conn, POSTGRES_MIGRATION_V2_RESOURCE);
}
} }
} }
@@ -104,8 +110,23 @@ public final class DatabaseInitializer {
} }
} }
private static void runPostgresSchemaScript(Connection conn) throws SQLException { private static int readCurrentSchemaVersion(Connection conn) throws SQLException {
String sqlScript = loadClasspathResource(POSTGRES_SCHEMA_RESOURCE); String sql = """
SELECT schema_version
FROM db_schema_version
WHERE id = 1
""";
try (Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql)) {
if (!rs.next()) {
return SCHEMA_VERSION_1;
}
return rs.getInt(1);
}
}
private static void runSqlScript(Connection conn, String resourcePath) throws SQLException {
String sqlScript = loadClasspathResource(resourcePath);
List<String> statements = splitSqlStatements(sqlScript); List<String> statements = splitSqlStatements(sqlScript);
boolean previousAutoCommit = conn.getAutoCommit(); boolean previousAutoCommit = conn.getAutoCommit();
@@ -0,0 +1,178 @@
BEGIN;
CREATE TABLE IF NOT EXISTS user_access_servers_current (
user_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
server_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
server_url TEXT NOT NULL,
server_client_key TEXT NOT NULL,
user_record_number INTEGER NOT NULL,
user_updated_at_ms BIGINT NOT NULL,
server_record_number INTEGER NOT NULL,
server_updated_at_ms BIGINT NOT NULL,
refreshed_at_ms BIGINT NOT NULL,
PRIMARY KEY (user_login, server_login)
);
CREATE INDEX IF NOT EXISTS idx_user_access_servers_user
ON user_access_servers_current(user_login);
CREATE INDEX IF NOT EXISTS idx_user_access_servers_server
ON user_access_servers_current(server_login);
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_user(p_user_login TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
IF p_user_login IS NULL OR btrim(p_user_login) = '' THEN
RETURN;
END IF;
DELETE FROM user_access_servers_current
WHERE LOWER(user_login) = LOWER(p_user_login);
INSERT INTO user_access_servers_current (
user_login,
server_login,
server_url,
server_client_key,
user_record_number,
user_updated_at_ms,
server_record_number,
server_updated_at_ms,
refreshed_at_ms
)
SELECT
u.login,
s.login,
s.server_address,
s.client_key,
u.record_number,
u.updated_at_ms,
s.record_number,
s.updated_at_ms,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
JOIN solana_user_pda_current s
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
AND s.is_server = TRUE
AND btrim(COALESCE(s.server_address, '')) <> ''
WHERE LOWER(u.login) = LOWER(p_user_login)
ON CONFLICT (user_login, server_login) DO UPDATE SET
server_url = EXCLUDED.server_url,
server_client_key = EXCLUDED.server_client_key,
user_record_number = EXCLUDED.user_record_number,
user_updated_at_ms = EXCLUDED.user_updated_at_ms,
server_record_number = EXCLUDED.server_record_number,
server_updated_at_ms = EXCLUDED.server_updated_at_ms,
refreshed_at_ms = EXCLUDED.refreshed_at_ms;
END;
$$;
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_server(p_server_login TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
affected_user RECORD;
BEGIN
IF p_server_login IS NULL OR btrim(p_server_login) = '' THEN
RETURN;
END IF;
DELETE FROM user_access_servers_current
WHERE LOWER(server_login) = LOWER(p_server_login);
FOR affected_user IN
SELECT u.login
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
END LOOP;
END;
$$;
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_all()
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
affected_user RECORD;
BEGIN
TRUNCATE TABLE user_access_servers_current;
FOR affected_user IN
SELECT login
FROM solana_user_pda_current
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
END LOOP;
END;
$$;
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_row()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
RETURN OLD;
END IF;
IF TG_OP = 'UPDATE' AND LOWER(OLD.login) <> LOWER(NEW.login) THEN
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
END IF;
PERFORM shine_refresh_user_access_servers_for_user(NEW.login);
PERFORM shine_refresh_user_access_servers_for_server(NEW.login);
RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
TRUNCATE TABLE user_access_servers_current;
RETURN NULL;
END;
$$;
DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_row ON solana_user_pda_current;
CREATE TRIGGER trg_refresh_user_access_servers_row
AFTER INSERT OR UPDATE OR DELETE ON solana_user_pda_current
FOR EACH ROW
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_row();
DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_truncate ON solana_user_pda_current;
CREATE TRIGGER trg_refresh_user_access_servers_truncate
AFTER TRUNCATE ON solana_user_pda_current
FOR EACH STATEMENT
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate();
SELECT shine_refresh_user_access_servers_all();
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
VALUES (1, 2, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT))
ON CONFLICT (id) DO UPDATE SET
schema_version = EXCLUDED.schema_version,
updated_at_ms = EXCLUDED.updated_at_ms;
COMMIT;
@@ -1,5 +1,5 @@
-- SHiNE PostgreSQL runtime schema v1 -- SHiNE PostgreSQL runtime schema v1
-- Дата: 2026-07-24 -- Дата: 2026-07-28
-- --
-- Назначение: -- Назначение:
-- - поднять пустую PostgreSQL БД сервера SHiNE с нуля; -- - поднять пустую PostgreSQL БД сервера SHiNE с нуля;
@@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS db_schema_version (
); );
INSERT INTO db_schema_version (id, schema_version, updated_at_ms) INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
VALUES (1, 1, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)) VALUES (1, 2, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT))
ON CONFLICT (id) DO UPDATE SET ON CONFLICT (id) DO UPDATE SET
schema_version = EXCLUDED.schema_version, schema_version = EXCLUDED.schema_version,
updated_at_ms = EXCLUDED.updated_at_ms; updated_at_ms = EXCLUDED.updated_at_ms;
@@ -109,6 +109,173 @@ CREATE TABLE IF NOT EXISTS solana_user_pda_current (
CREATE INDEX IF NOT EXISTS idx_user_pda_current_slot CREATE INDEX IF NOT EXISTS idx_user_pda_current_slot
ON solana_user_pda_current(slot); ON solana_user_pda_current(slot);
CREATE TABLE IF NOT EXISTS user_access_servers_current (
user_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
server_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
server_url TEXT NOT NULL,
server_client_key TEXT NOT NULL,
user_record_number INTEGER NOT NULL,
user_updated_at_ms BIGINT NOT NULL,
server_record_number INTEGER NOT NULL,
server_updated_at_ms BIGINT NOT NULL,
refreshed_at_ms BIGINT NOT NULL,
PRIMARY KEY (user_login, server_login)
);
CREATE INDEX IF NOT EXISTS idx_user_access_servers_user
ON user_access_servers_current(user_login);
CREATE INDEX IF NOT EXISTS idx_user_access_servers_server
ON user_access_servers_current(server_login);
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_user(p_user_login TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
IF p_user_login IS NULL OR btrim(p_user_login) = '' THEN
RETURN;
END IF;
DELETE FROM user_access_servers_current
WHERE LOWER(user_login) = LOWER(p_user_login);
INSERT INTO user_access_servers_current (
user_login,
server_login,
server_url,
server_client_key,
user_record_number,
user_updated_at_ms,
server_record_number,
server_updated_at_ms,
refreshed_at_ms
)
SELECT
u.login,
s.login,
s.server_address,
s.client_key,
u.record_number,
u.updated_at_ms,
s.record_number,
s.updated_at_ms,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
JOIN solana_user_pda_current s
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
AND s.is_server = TRUE
AND btrim(COALESCE(s.server_address, '')) <> ''
WHERE LOWER(u.login) = LOWER(p_user_login)
ON CONFLICT (user_login, server_login) DO UPDATE SET
server_url = EXCLUDED.server_url,
server_client_key = EXCLUDED.server_client_key,
user_record_number = EXCLUDED.user_record_number,
user_updated_at_ms = EXCLUDED.user_updated_at_ms,
server_record_number = EXCLUDED.server_record_number,
server_updated_at_ms = EXCLUDED.server_updated_at_ms,
refreshed_at_ms = EXCLUDED.refreshed_at_ms;
END;
$$;
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_server(p_server_login TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
affected_user RECORD;
BEGIN
IF p_server_login IS NULL OR btrim(p_server_login) = '' THEN
RETURN;
END IF;
DELETE FROM user_access_servers_current
WHERE LOWER(server_login) = LOWER(p_server_login);
FOR affected_user IN
SELECT u.login
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
END LOOP;
END;
$$;
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_all()
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
affected_user RECORD;
BEGIN
TRUNCATE TABLE user_access_servers_current;
FOR affected_user IN
SELECT login
FROM solana_user_pda_current
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
END LOOP;
END;
$$;
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_row()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
RETURN OLD;
END IF;
IF TG_OP = 'UPDATE' AND LOWER(OLD.login) <> LOWER(NEW.login) THEN
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
END IF;
PERFORM shine_refresh_user_access_servers_for_user(NEW.login);
PERFORM shine_refresh_user_access_servers_for_server(NEW.login);
RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
TRUNCATE TABLE user_access_servers_current;
RETURN NULL;
END;
$$;
DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_row ON solana_user_pda_current;
CREATE TRIGGER trg_refresh_user_access_servers_row
AFTER INSERT OR UPDATE OR DELETE ON solana_user_pda_current
FOR EACH ROW
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_row();
DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_truncate ON solana_user_pda_current;
CREATE TRIGGER trg_refresh_user_access_servers_truncate
AFTER TRUNCATE ON solana_user_pda_current
FOR EACH STATEMENT
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate();
CREATE TABLE IF NOT EXISTS solana_user_pda_history ( CREATE TABLE IF NOT EXISTS solana_user_pda_history (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
tx_signature TEXT NOT NULL, tx_signature TEXT NOT NULL,
@@ -996,6 +996,208 @@ public final class PostgresStorageRepository
"CREATE INDEX IF NOT EXISTS idx_user_pda_history_slot " + "CREATE INDEX IF NOT EXISTS idx_user_pda_history_slot " +
"ON solana_user_pda_history(slot)" "ON solana_user_pda_history(slot)"
); );
statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS user_access_servers_current (" +
"user_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE, " +
"server_login TEXT NOT NULL REFERENCES solana_user_pda_current(login) ON DELETE CASCADE, " +
"server_url TEXT NOT NULL, " +
"server_client_key TEXT NOT NULL, " +
"user_record_number INTEGER NOT NULL, " +
"user_updated_at_ms BIGINT NOT NULL, " +
"server_record_number INTEGER NOT NULL, " +
"server_updated_at_ms BIGINT NOT NULL, " +
"refreshed_at_ms BIGINT NOT NULL, " +
"PRIMARY KEY (user_login, server_login)" +
")"
);
statement.executeUpdate(
"CREATE INDEX IF NOT EXISTS idx_user_access_servers_user " +
"ON user_access_servers_current(user_login)"
);
statement.executeUpdate(
"CREATE INDEX IF NOT EXISTS idx_user_access_servers_server " +
"ON user_access_servers_current(server_login)"
);
statement.execute(
"""
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_user(p_user_login TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
IF p_user_login IS NULL OR btrim(p_user_login) = '' THEN
RETURN;
END IF;
DELETE FROM user_access_servers_current
WHERE LOWER(user_login) = LOWER(p_user_login);
INSERT INTO user_access_servers_current (
user_login,
server_login,
server_url,
server_client_key,
user_record_number,
user_updated_at_ms,
server_record_number,
server_updated_at_ms,
refreshed_at_ms
)
SELECT
u.login,
s.login,
s.server_address,
s.client_key,
u.record_number,
u.updated_at_ms,
s.record_number,
s.updated_at_ms,
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
JOIN solana_user_pda_current s
ON LOWER(s.login) = LOWER(btrim(access_server.login_value))
AND s.is_server = TRUE
AND btrim(COALESCE(s.server_address, '')) <> ''
WHERE LOWER(u.login) = LOWER(p_user_login)
ON CONFLICT (user_login, server_login) DO UPDATE SET
server_url = EXCLUDED.server_url,
server_client_key = EXCLUDED.server_client_key,
user_record_number = EXCLUDED.user_record_number,
user_updated_at_ms = EXCLUDED.user_updated_at_ms,
server_record_number = EXCLUDED.server_record_number,
server_updated_at_ms = EXCLUDED.server_updated_at_ms,
refreshed_at_ms = EXCLUDED.refreshed_at_ms;
END;
$$;
"""
);
statement.execute(
"""
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_for_server(p_server_login TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
affected_user RECORD;
BEGIN
IF p_server_login IS NULL OR btrim(p_server_login) = '' THEN
RETURN;
END IF;
DELETE FROM user_access_servers_current
WHERE LOWER(server_login) = LOWER(p_server_login);
FOR affected_user IN
SELECT u.login
FROM solana_user_pda_current u
CROSS JOIN LATERAL jsonb_array_elements_text(
CASE
WHEN btrim(COALESCE(u.access_servers_json, '')) = '' THEN '[]'::jsonb
ELSE u.access_servers_json::jsonb
END
) AS access_server(login_value)
WHERE LOWER(btrim(access_server.login_value)) = LOWER(p_server_login)
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
END LOOP;
END;
$$;
"""
);
statement.execute(
"""
CREATE OR REPLACE FUNCTION shine_refresh_user_access_servers_all()
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
affected_user RECORD;
BEGIN
TRUNCATE TABLE user_access_servers_current;
FOR affected_user IN
SELECT login
FROM solana_user_pda_current
LOOP
PERFORM shine_refresh_user_access_servers_for_user(affected_user.login);
END LOOP;
END;
$$;
"""
);
statement.execute(
"""
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_row()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
RETURN OLD;
END IF;
IF TG_OP = 'UPDATE' AND LOWER(OLD.login) <> LOWER(NEW.login) THEN
PERFORM shine_refresh_user_access_servers_for_user(OLD.login);
PERFORM shine_refresh_user_access_servers_for_server(OLD.login);
END IF;
PERFORM shine_refresh_user_access_servers_for_user(NEW.login);
PERFORM shine_refresh_user_access_servers_for_server(NEW.login);
RETURN NEW;
END;
$$;
"""
);
statement.execute(
"""
CREATE OR REPLACE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
TRUNCATE TABLE user_access_servers_current;
RETURN NULL;
END;
$$;
"""
);
statement.executeUpdate(
"DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_row ON solana_user_pda_current"
);
statement.executeUpdate(
"CREATE TRIGGER trg_refresh_user_access_servers_row " +
"AFTER INSERT OR UPDATE OR DELETE ON solana_user_pda_current " +
"FOR EACH ROW " +
"EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_row()"
);
statement.executeUpdate(
"DROP TRIGGER IF EXISTS trg_refresh_user_access_servers_truncate ON solana_user_pda_current"
);
statement.executeUpdate(
"CREATE TRIGGER trg_refresh_user_access_servers_truncate " +
"AFTER TRUNCATE ON solana_user_pda_current " +
"FOR EACH STATEMENT " +
"EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate()"
);
statement.execute("SELECT shine_refresh_user_access_servers_all()");
} }
} }
+1 -1
View File
@@ -1,2 +1,2 @@
client.version=1.2.355 client.version=1.2.355
server.version=1.2.338 server.version=1.2.339
+20 -10
View File
@@ -2,7 +2,11 @@
Этот документ фиксирует целевой стандарт PostgreSQL, который должен использоваться на всех серверных контурах SHiNE после ухода от SQLite. Этот документ фиксирует целевой стандарт PostgreSQL, который должен использоваться на всех серверных контурах SHiNE после ухода от SQLite.
Пока это не текущий production-state, а согласованная целевая схема, под которую можно писать миграции, deploy-скрипты и server-конфиги. Важно:
- целевой стандарт для SHiNE: `PostgreSQL 18.x`;
- текущий факт на production `shineup.me` и `server2.shineup.me` на дату `2026-07-28`: `PostgreSQL 18.4`;
- текущий факт на test/devnet `t1` / `t2` / `t3` / `t4` на дату `2026-07-28`: `PostgreSQL 18.4`.
## Цель ## Цель
@@ -71,19 +75,19 @@ PostgreSQL не должна быть доступна из интернета.
Production: Production:
```text ```text
/home/player/SHiNE/postgres/shine_server_db /home/player/SHiNE/postgres/data
``` ```
Test/devnet: Test/devnet:
```text ```text
/home/player/tX/postgres/shine_server_db /home/player/tX/postgres/data
``` ```
Если на одном сервере появится несколько баз SHiNE-сервисов, раскладывать их по отдельным каталогам: Если на одном сервере появится несколько баз SHiNE-сервисов, раскладывать их по отдельным каталогам:
```text ```text
/home/player/SHiNE/postgres/shine_server_db /home/player/SHiNE/postgres/data
/home/player/SHiNE/postgres/analytics_db /home/player/SHiNE/postgres/analytics_db
/home/player/SHiNE/postgres/other_service_db /home/player/SHiNE/postgres/other_service_db
``` ```
@@ -142,11 +146,10 @@ services:
POSTGRES_DB: ${POSTGRES_SUPERUSER_DB} POSTGRES_DB: ${POSTGRES_SUPERUSER_DB}
POSTGRES_USER: ${POSTGRES_SUPERUSER} POSTGRES_USER: ${POSTGRES_SUPERUSER}
POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
ports: ports:
- "127.0.0.1:5432:5432" - "127.0.0.1:5432:5432"
volumes: volumes:
- ${SHINE_POSTGRES_DATA_DIR}:/var/lib/postgresql/data - ${SHINE_POSTGRES_DATA_DIR}:/var/lib/postgresql
- ./initdb:/docker-entrypoint-initdb.d:ro - ./initdb:/docker-entrypoint-initdb.d:ro
healthcheck: healthcheck:
test: test:
@@ -167,15 +170,21 @@ POSTGRES_SUPERUSER_PASSWORD=change_me
SHINE_APP_DB=shine_server_db SHINE_APP_DB=shine_server_db
SHINE_APP_USER=shine_server SHINE_APP_USER=shine_server
SHINE_APP_PASSWORD=change_me_too SHINE_APP_PASSWORD=change_me_too
SHINE_POSTGRES_DATA_DIR=/home/player/SHiNE/postgres/shine_server_db SHINE_POSTGRES_DATA_DIR=/home/player/SHiNE/postgres/data
``` ```
Для test/devnet путь адаптировать под конкретный контур: Для test/devnet путь адаптировать под конкретный контур:
```dotenv ```dotenv
SHINE_POSTGRES_DATA_DIR=/home/player/t2/postgres/shine_server_db SHINE_POSTGRES_DATA_DIR=/home/player/t2/postgres/data
``` ```
Важно для `postgres:18+`:
- bind-mount нужно делать на `/var/lib/postgresql`, а не на `/var/lib/postgresql/data`;
- если смонтировать старый путь `/var/lib/postgresql/data`, контейнер `postgres:18` может уйти в restart-loop ещё до инициализации БД;
- после первого старта PostgreSQL сам создаст внутри host-каталога структуру вида `18/docker`.
## Подключение приложения ## Подключение приложения
SHiNE server должен подключаться именно к: SHiNE server должен подключаться именно к:
@@ -203,11 +212,12 @@ SHiNE server должен подключаться именно к:
## Для будущей миграции SHiNE ## Для будущей миграции SHiNE
При переводе SHiNE server с SQLite на PostgreSQL считать обязательным: При переводе нового серверного контура SHiNE с SQLite на PostgreSQL считать обязательным:
- сначала поднять PostgreSQL по этому стандарту; - сначала поднять PostgreSQL по этому стандарту;
- затем добавить серверные конфиги подключения; - затем добавить серверные конфиги подключения;
- затем прогнать миграции схемы; - затем прогнать миграции схемы;
- только потом переключать runtime приложения на PostgreSQL. - только потом переключать runtime приложения на PostgreSQL.
До фактического rollout на конкретный сервер эта БД может ещё отсутствовать. Этот документ описывает не текущее наличие БД, а обязательный целевой стандарт для всех серверов SHiNE. Для новых серверов этот документ использовать как обязательный шаблон.
На `2026-07-28` и production, и test/devnet контуры SHiNE уже работают на `PostgreSQL 18.4`.
+29
View File
@@ -2,6 +2,13 @@
Production-контуров два. В документах и скриптах использовать домены, а не IP: физический VPS можно заменить без изменения deploy-логики. Production-контуров два. В документах и скриптах использовать домены, а не IP: физический VPS можно заменить без изменения deploy-логики.
Текущее фактическое состояние на `2026-07-28`:
- `shineup.me` и `server2.shineup.me` оба уже переведены на `PostgreSQL 18.4`;
- оба production используют локальный Docker-контейнер `shine-postgres`;
- оба production работают без `SQLite`;
- оба production используют Helius mainnet RPC/WS для Solana users sync и runtime Solana RPC.
## Основной production ## Основной production
- Домен: `shineup.me` - Домен: `shineup.me`
@@ -16,6 +23,17 @@ Production-контуров два. В документах и скриптах
- Caddy site: `shineup.me` - Caddy site: `shineup.me`
- WebSocket: `/ws` -> `127.0.0.1:7070` - WebSocket: `/ws` -> `127.0.0.1:7070`
- Solana cluster: `mainnet-beta` - Solana cluster: `mainnet-beta`
- Solana RPC/WS: Helius mainnet
- Runtime DB: PostgreSQL в Docker
- PostgreSQL container: `shine-postgres`
- PostgreSQL image: `postgres:18`
- PostgreSQL engine version на `2026-07-28`: `18.4`
- PostgreSQL data dir: `/home/player/SHiNE/postgres`
- PostgreSQL app DB: `shine_main_db`
- PostgreSQL app user: `shine_main`
- Web push: отдельный production VAPID key для `shineup.me`
- TURN для звонков: `turn1.shineup.me`, `turn2.shineup.me`
- SQLite runtime больше не используется
Deploy: Deploy:
@@ -38,6 +56,17 @@ bash deploy/scripts/production_shineupme_ui.sh
- Caddy site: `server2.shineup.me` - Caddy site: `server2.shineup.me`
- WebSocket: `/ws` -> `127.0.0.1:7070` - WebSocket: `/ws` -> `127.0.0.1:7070`
- Solana cluster: `mainnet-beta` - Solana cluster: `mainnet-beta`
- Solana RPC/WS: Helius mainnet
- Runtime DB: PostgreSQL в Docker
- PostgreSQL container: `shine-postgres`
- PostgreSQL image: `postgres:18`
- PostgreSQL engine version на `2026-07-28`: `18.4`
- PostgreSQL data dir: `/home/player/SHiNE/postgres`
- PostgreSQL app DB: `shine_server2_db`
- PostgreSQL app user: `shine_server2`
- Web push: отдельный production VAPID key для `server2.shineup.me`
- TURN для звонков: `turn1.shineup.me`, `turn3.shineup.me`
- SQLite runtime больше не используется
Deploy: Deploy:
+8
View File
@@ -15,6 +15,14 @@
- Второй production: `server2.shineup.me`. - Второй production: `server2.shineup.me`.
- Test/devnet стенд: `t1.shineup.me`, `t2.shineup.me`, `t3.shineup.me`, `t4.shineup.me`. - Test/devnet стенд: `t1.shineup.me`, `t2.shineup.me`, `t3.shineup.me`, `t4.shineup.me`.
Краткое текущее состояние на `2026-07-28`:
- оба production уже на `PostgreSQL 18.4` в отдельных Docker-контейнерах `shine-postgres`;
- весь test/devnet стенд `t1`-`t4` тоже уже на `PostgreSQL 18.4`;
- тестовые контуры используют общий Docker-контейнер `shine-test-postgres`;
- production использует Helius mainnet, test/devnet использует Helius devnet;
- актуальные подробности по всем шести серверам смотреть в `PRODUCTION_SERVERS.md` и `TEST_SERVERS.md`.
## Основные документы ## Основные документы
- `PRODUCTION_SERVERS.md` — production-контуры. - `PRODUCTION_SERVERS.md` — production-контуры.
+14 -8
View File
@@ -41,16 +41,19 @@ mkdir -p /home/player/tX/UI
Каждый сервер должен иметь локальный внешний конфиг в рабочей директории сервиса. Каждый сервер должен иметь локальный внешний конфиг в рабочей директории сервиса.
Актуальные production-значения для `shineup.me` и `server2.shineup.me` смотреть в `deploy/PRODUCTION_SERVERS.md`.
Ниже именно шаблон для нового контура, а не снимок текущего production.
Production пример: Production пример:
```properties ```properties
server.port=7070 server.port=7070
server.SHiNE.login=shineupme server.SHiNE.login=<SERVER_LOGIN>
db.url=jdbc:postgresql://127.0.0.1:5432/shine_server_db db.url=jdbc:postgresql://127.0.0.1:5432/<APP_DB>
db.user=shine_server db.user=<APP_DB_USER>
db.password=CHANGE_ME db.password=CHANGE_ME
server.ui.indexPath=/home/player/SHiNE/shine-ui/index.html server.ui.indexPath=/home/player/SHiNE/shine-ui/index.html
server.info.url=https://shineup.me server.info.url=https://<DOMAIN>
server.info.origin=production server.info.origin=production
solana.cluster=mainnet-beta solana.cluster=mainnet-beta
``` ```
@@ -59,17 +62,20 @@ Test/devnet пример:
```properties ```properties
server.port=7102 server.port=7102
server.SHiNE.login=server_t2 server.SHiNE.login=<SERVER_LOGIN>
db.url=jdbc:postgresql://127.0.0.1:5432/shine_t2_db db.url=jdbc:postgresql://127.0.0.1:5432/<APP_DB>
db.user=shine_t2 db.user=<APP_DB_USER>
db.password=CHANGE_ME db.password=CHANGE_ME
server.ui.indexPath=/home/player/t2/UI/index.html server.ui.indexPath=/home/player/t2/UI/index.html
server.info.url=https://t2.shineup.me server.info.url=https://t2.shineup.me
server.info.origin=devnet server.info.origin=devnet
solana.cluster=devnet solana.cluster=devnet
solana.rpcUrl=https://api.devnet.solana.com solana.rpcUrl=https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4
``` ```
Рекомендуемый образ PostgreSQL для новых контуров: `postgres:18`.
Для `postgres:18+` bind-mount нужно делать на `/var/lib/postgresql`, а не на старый путь `/var/lib/postgresql/data`.
Если указан `db.url` и в выбранной БД ещё нет таблицы `db_schema_version`, Если указан `db.url` и в выбранной БД ещё нет таблицы `db_schema_version`,
сервер сам создаст runtime-схему PostgreSQL при первом старте. сервер сам создаст runtime-схему PostgreSQL при первом старте.
+16 -6
View File
@@ -10,17 +10,23 @@
- `solana_sync_state` - `solana_sync_state`
- `solana_sync_tx_history` - `solana_sync_tx_history`
- `solana_user_pda_current` - `solana_user_pda_current`
- `user_access_servers_current`
- `solana_user_pda_history` - `solana_user_pda_history`
- источник истины по пользовательским PDA: `solana_user_pda_current`. - источник истины по пользовательским PDA: `solana_user_pda_current`.
- `user_access_servers_current` — это вторичная локальная проекция для быстрого роутинга DM по access servers;
она автоматически пересобирается из `solana_user_pda_current`, включая backfill для уже существующих пользователей.
## Что должно быть настроено в `application.properties` ## Что должно быть настроено в `application.properties`
Для текущих production-значений `shineup.me` и `server2.shineup.me` смотреть `deploy/PRODUCTION_SERVERS.md`.
Ниже универсальный пример для нового контура.
```properties ```properties
solana.users.sync.enabled=true solana.users.sync.enabled=true
solana.users.sync.rpcUrl=https://api.devnet.solana.com solana.users.sync.rpcUrl=https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4
solana.users.sync.wsUrl=wss://api.devnet.solana.com/ solana.users.sync.wsUrl=wss://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4
solana.users.sync.databaseUrl=jdbc:postgresql://127.0.0.1:5432/shine_server_db solana.users.sync.databaseUrl=jdbc:postgresql://127.0.0.1:5432/<APP_DB>
solana.users.sync.dbUser=shine_server solana.users.sync.dbUser=<APP_DB_USER>
solana.users.sync.dbPassword=CHANGE_ME solana.users.sync.dbPassword=CHANGE_ME
solana.users.sync.pollIntervalSeconds=300 solana.users.sync.pollIntervalSeconds=300
``` ```
@@ -43,16 +49,17 @@ SHiNE-server/shine-server-db/src/main/resources/postgres/schema_v1.sql
```bash ```bash
psql \ psql \
"postgresql://shine_server:CHANGE_ME@127.0.0.1:5432/shine_server_db" \ "postgresql://<APP_DB_USER>:CHANGE_ME@127.0.0.1:5432/<APP_DB>" \
-f SHiNE-server/shine-server-db/src/main/resources/postgres/schema_v1.sql -f SHiNE-server/shine-server-db/src/main/resources/postgres/schema_v1.sql
``` ```
Скрипт: Скрипт:
- создаёт таблицу версии схемы `db_schema_version`; - создаёт таблицу версии схемы `db_schema_version`;
- ставит `schema_version = 1`; - ставит `schema_version = 2`;
- создаёт таблицы sync-модуля Solana users; - создаёт таблицы sync-модуля Solana users;
- создаёт server runtime tables; - создаёт server runtime tables;
- создаёт триггеры и функции автоматической актуализации `user_access_servers_current`;
- не создаёт удалённые legacy-таблицы старого runtime для пользователей и DM; - не создаёт удалённые legacy-таблицы старого runtime для пользователей и DM;
- использует `signed_messages` как единственную таблицу серверных DM. - использует `signed_messages` как единственную таблицу серверных DM.
@@ -75,6 +82,9 @@ cd /home/player/SHiNE/postgres
docker compose up -d docker compose up -d
``` ```
Шаблон `deploy/postgres/docker-compose.yml.example` рассчитан на `postgres:18`.
Для `postgres:18+` он монтирует host-каталог в `/var/lib/postgresql`, это важно для корректного старта контейнера.
После старта контейнера: После старта контейнера:
```bash ```bash
+28 -8
View File
@@ -9,21 +9,34 @@
Это не production. Все deploy-цели задаются через домены. Это не production. Все deploy-цели задаются через домены.
Текущее фактическое состояние на `2026-07-28`:
- все четыре тестовых контура `t1` / `t2` / `t3` / `t4` уже работают без `SQLite`;
- все четыре контура используют `PostgreSQL 18.4`;
- PostgreSQL на test-хосте общий для всех четырёх контуров и запущен в Docker-контейнере `shine-test-postgres`;
- все четыре контура используют один и тот же Helius devnet RPC/WS endpoint;
- во всех четырёх UI включён web push client-код и встроен один и тот же test `VAPID` public key.
## Общие параметры ## Общие параметры
- SSH: `player@tX.shineup.me` - SSH: `player@tX.shineup.me`
- Solana cluster: `devnet` - Solana cluster: `devnet`
- Solana RPC: `https://api.devnet.solana.com` - Solana RPC: `https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4`
- Solana WS: `wss://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4`
- Caddy config: `/etc/caddy/Caddyfile` - Caddy config: `/etc/caddy/Caddyfile`
- Общий PostgreSQL container: `shine-test-postgres`
- PostgreSQL image: `postgres:18`
- PostgreSQL engine version на `2026-07-28`: `18.4`
- Web push в UI: общий test `VAPID` public key `BOdoWZndZRaNe9kyUFsJ5-xEfFABXNKennAKg15Z7ycAwUIQ7yDV_sIWWYJCwJriN4g9oU-CyJPrn1U6lfxuDbI`
## Инстансы ## Инстансы
| Домен | Логин сервера | Сервер | UI | Порт | systemd | | Домен | Логин сервера | Сервер | UI | Порт | systemd | PostgreSQL DB | PostgreSQL user |
|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|
| `t1.shineup.me` | `server_t1` | `/home/player/t1/server` | `/home/player/t1/UI` | `7101` | `shine-t1.service` | | `t1.shineup.me` | `server_t1` | `/home/player/t1/server` | `/home/player/t1/UI` | `7101` | `shine-t1.service` | `shine_t1_db` | `shine_t1` |
| `t2.shineup.me` | `server_t2` | `/home/player/t2/server` | `/home/player/t2/UI` | `7102` | `shine-t2.service` | | `t2.shineup.me` | `server_t2` | `/home/player/t2/server` | `/home/player/t2/UI` | `7102` | `shine-t2.service` | `shine_t2_db` | `shine_t2` |
| `t3.shineup.me` | `server_t3` | `/home/player/t3/server` | `/home/player/t3/UI` | `7103` | `shine-t3.service` | | `t3.shineup.me` | `server_t3` | `/home/player/t3/server` | `/home/player/t3/UI` | `7103` | `shine-t3.service` | `shine_t3_db` | `shine_t3` |
| `t4.shineup.me` | `server_t4` | `/home/player/t4/server` | `/home/player/t4/UI` | `7104` | `shine-t4.service` | | `t4.shineup.me` | `server_t4` | `/home/player/t4/server` | `/home/player/t4/UI` | `7104` | `shine-t4.service` | `shine_t4_db` | `shine_t4` |
## Deploy ## Deploy
@@ -54,8 +67,15 @@ curl -I https://t4.shineup.me
```bash ```bash
sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4 sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
``` ```
## Operational-нюанс ## Operational-нюанс
Официальный `https://api.devnet.solana.com` может отдавать `HTTP 429`, если несколько инстансов одновременно делают bootstrap PDA/sync-серверов. Если после деплоя какой-то инстанс не подтянул `sync_servers`, рестартовать сервисы по одному с паузой. Общий Helius devnet endpoint тоже может начать ограничивать запросы, если несколько инстансов одновременно делают тяжёлый bootstrap. Если после деплоя какой-то инстанс не подтянул `sync_servers` или завис на startup sync, рестартовать сервисы по одному с паузой.
Отдельный нюанс по web push:
- у `t1`, `t2`, `t3`, `t4` в UI уже есть одинаковый встроенный `VAPID` public key;
- то есть `t3` и `t4` используют тот же web push клиентский код, что и `t1` / `t2`;
- если позже понадобится разделить push-ключи по тестовым контурам, это нужно делать отдельным UI deploy с новым `DEPLOY_WEBPUSH_VAPID_PUBLIC`.
+1 -1
View File
@@ -4,4 +4,4 @@ POSTGRES_SUPERUSER_PASSWORD=CHANGE_ME_SUPERUSER
SHINE_APP_DB=shine_server_db SHINE_APP_DB=shine_server_db
SHINE_APP_USER=shine_server SHINE_APP_USER=shine_server
SHINE_APP_PASSWORD=CHANGE_ME_APP SHINE_APP_PASSWORD=CHANGE_ME_APP
SHINE_POSTGRES_DATA_DIR=/home/player/SHiNE/postgres/shine_server_db SHINE_POSTGRES_DATA_DIR=/home/player/SHiNE/postgres/data
+1 -2
View File
@@ -9,11 +9,10 @@ services:
POSTGRES_DB: ${POSTGRES_SUPERUSER_DB} POSTGRES_DB: ${POSTGRES_SUPERUSER_DB}
POSTGRES_USER: ${POSTGRES_SUPERUSER} POSTGRES_USER: ${POSTGRES_SUPERUSER}
POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
ports: ports:
- "127.0.0.1:5432:5432" - "127.0.0.1:5432:5432"
volumes: volumes:
- ${SHINE_POSTGRES_DATA_DIR}:/var/lib/postgresql/data - ${SHINE_POSTGRES_DATA_DIR}:/var/lib/postgresql
healthcheck: healthcheck:
test: test:
- CMD-SHELL - CMD-SHELL
+1 -1
View File
@@ -8,6 +8,6 @@ EXPECTED_CADDY_SITE="t1.shineup.me" \
DEPLOY_SERVER_LOGIN="server_t1" \ DEPLOY_SERVER_LOGIN="server_t1" \
DEPLOY_SERVER_ADDRESS="t1.shineup.me" \ DEPLOY_SERVER_ADDRESS="t1.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \ DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \ DEPLOY_SOLANA_ENDPOINT="https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4" \
TARGET_URL="https://t1.shineup.me" \ TARGET_URL="https://t1.shineup.me" \
bash "$(dirname "$0")/deploy_ui.sh" bash "$(dirname "$0")/deploy_ui.sh"
+1 -1
View File
@@ -8,6 +8,6 @@ EXPECTED_CADDY_SITE="t2.shineup.me" \
DEPLOY_SERVER_LOGIN="server_t2" \ DEPLOY_SERVER_LOGIN="server_t2" \
DEPLOY_SERVER_ADDRESS="t2.shineup.me" \ DEPLOY_SERVER_ADDRESS="t2.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \ DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \ DEPLOY_SOLANA_ENDPOINT="https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4" \
TARGET_URL="https://t2.shineup.me" \ TARGET_URL="https://t2.shineup.me" \
bash "$(dirname "$0")/deploy_ui.sh" bash "$(dirname "$0")/deploy_ui.sh"
+1 -1
View File
@@ -8,6 +8,6 @@ EXPECTED_CADDY_SITE="t3.shineup.me" \
DEPLOY_SERVER_LOGIN="server_t3" \ DEPLOY_SERVER_LOGIN="server_t3" \
DEPLOY_SERVER_ADDRESS="t3.shineup.me" \ DEPLOY_SERVER_ADDRESS="t3.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \ DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \ DEPLOY_SOLANA_ENDPOINT="https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4" \
TARGET_URL="https://t3.shineup.me" \ TARGET_URL="https://t3.shineup.me" \
bash "$(dirname "$0")/deploy_ui.sh" bash "$(dirname "$0")/deploy_ui.sh"
+1 -1
View File
@@ -8,6 +8,6 @@ EXPECTED_CADDY_SITE="t4.shineup.me" \
DEPLOY_SERVER_LOGIN="server_t4" \ DEPLOY_SERVER_LOGIN="server_t4" \
DEPLOY_SERVER_ADDRESS="t4.shineup.me" \ DEPLOY_SERVER_ADDRESS="t4.shineup.me" \
DEPLOY_SOLANA_CLUSTER="devnet" \ DEPLOY_SOLANA_CLUSTER="devnet" \
DEPLOY_SOLANA_ENDPOINT="https://api.devnet.solana.com" \ DEPLOY_SOLANA_ENDPOINT="https://devnet.helius-rpc.com/?api-key=0614c894-52d8-4ddc-bbbc-0947ce5ec3a4" \
TARGET_URL="https://t4.shineup.me" \ TARGET_URL="https://t4.shineup.me" \
bash "$(dirname "$0")/deploy_ui.sh" bash "$(dirname "$0")/deploy_ui.sh"
@@ -20,6 +20,8 @@
- сервер запускает sync-модуль до продолжения собственного startup; - сервер запускает sync-модуль до продолжения собственного startup;
- server startup ждёт входа модуля в состояние `READY`; - server startup ждёт входа модуля в состояние `READY`;
- актуальный источник истины по пользователям для нового PostgreSQL runtime-слоя: `solana_user_pda_current`. - актуальный источник истины по пользователям для нового PostgreSQL runtime-слоя: `solana_user_pda_current`.
- для быстрого локального маршрутизационного lookup серверов доступа поддерживается вторичная проекция
`user_access_servers_current`, которая автоматически пересобирается из `solana_user_pda_current`.
--- ---
@@ -193,6 +195,7 @@ WebSocket `programSubscribe` хорош для realtime, но недостато
6. подтянуть актуальное состояние затронутых PDA; 6. подтянуть актуальное состояние затронутых PDA;
7. обновить: 7. обновить:
- `solana_user_pda_current` - `solana_user_pda_current`
- `user_access_servers_current`
- `solana_user_pda_history` - `solana_user_pda_history`
- `solana_sync_state` - `solana_sync_state`