plugins { id 'java' id 'application' id 'com.github.johnrengelman.shadow' version '8.1.1' } def appVersionProps = new Properties() def appVersionFile = file('VERSION.properties') if (appVersionFile.exists()) { appVersionFile.withInputStream { appVersionProps.load(it) } } def serverVersionFromFile = String.valueOf(appVersionProps.getProperty('server.version', '1.1_codex')).trim() allprojects { tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } } group = 'shine' version = serverVersionFromFile layout.buildDirectory.set(file('SHiNE-server/build')) sourceSets { main { java.srcDirs = ['SHiNE-server/src/main/java'] resources.srcDirs = ['SHiNE-server/src/main/resources'] } test { java.srcDirs = ['SHiNE-server/src/test/java'] resources.srcDirs = ['SHiNE-server/src/test/resources'] } } tasks.jar { enabled = false // это что бы не создавала обычный джар, а будет только толстый джар } tasks.processResources { filteringCharset = 'UTF-8' filesMatching('application.properties') { expand(projectVersion: project.version.toString()) } } repositories { mavenCentral() } dependencies { implementation 'org.eclipse.jetty:jetty-server:11.0.20' // WS сервер implementation 'org.eclipse.jetty:jetty-servlet:11.0.20' implementation 'org.eclipse.jetty.websocket:websocket-jetty-server:11.0.20' implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1' // шифрование implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json // implementation 'org.slf4j:slf4j-api:2.0.9' implementation 'ch.qos.logback:logback-classic:1.5.6' // Logback (реализация SLF4J + классический модуль) runtimeOnly "ch.qos.logback:logback-classic:1.5.6" testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' implementation "org.slf4j:slf4j-api:2.0.16" // вызов логгера // runtimeOnly "org.apache.logging.log4j:log4j-core:2.24.3" // Реализация: Log4j2 пишет в файл/консоль // runtimeOnly "org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3" // Реализация: Log4j2 пишет в файл/консоль implementation project(':shine-server-config') // модуль настроек из application.properties implementation project(":shine-server-log") // модуль логирования и уведомления админов implementation project(':shine-server-crypto') // модуль сервера для работы с криптографией implementation project(':shine-server-db') // модуль для работы с БД содержит и сущности из БД и саму работу с БД implementation project(':shine-server-blockchain') // модуль для работы с блокчейном implementation project(':shine-server-geo') // модуль для определения геолокации по IP implementation project(':shine-server-net-protocol') // Модуль отвечающий за протокол (классы Net..Request/Response implementation project(':shine-server-net-server') // Хэндлеры для обработки сетевых запросов // -------------------- ТЕСТЫ (JUnit 5) -------------------- // Один BOM на всё семейство JUnit (Jupiter + Platform модули) testImplementation platform("org.junit:junit-bom:5.10.2") // JUnit Jupiter (Test, BeforeAll, Assertions) testImplementation "org.junit.jupiter:junit-jupiter" // JUnit Platform Suite (@Suite, @SelectClasses) testImplementation "org.junit.platform:junit-platform-suite-api" testRuntimeOnly "org.junit.platform:junit-platform-suite-engine" // Нужно для компиляции RussianSummaryListener (org.junit.platform.launcher.*) // и чтобы JUnit мог подхватить listener при запуске testImplementation "org.junit.platform:junit-platform-launcher" testRuntimeOnly "org.junit.platform:junit-platform-launcher" } test { useJUnitPlatform() } application { // 👇 класс с методом main mainClass = 'server.ws.WsServer' } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } shadowJar { // создаём 1 файл без постфиксов archiveBaseName.set('shine-server') archiveClassifier.set('') archiveVersion.set('') mergeServiceFiles() manifest { attributes( 'Main-Class': 'server.ws.WsServer' ) } } tasks.named('test') { enabled = false } tasks.register('cleanServerLogs') { group = "!!test" description = "Clear server logs/app.log and remove rolled log files" doLast { File logsDir = file('SHiNE-server/logs') if (!logsDir.exists()) { logsDir.mkdirs() } File appLog = new File(logsDir, 'app.log') if (!appLog.exists()) { appLog.createNewFile() } appLog.text = '' fileTree(logsDir) { include 'app.*.log' }.files.each { File f -> if (!f.delete()) { throw new GradleException("Failed to delete log file: ${f.absolutePath}") } } println "Server logs cleared: ${logsDir.absolutePath}" } } tasks.register('integrationTest', JavaExec) { group = "!!test" description = "Clean data → kill 7070 → start WS → run all IT tests" classpath = sourceSets.test.runtimeClasspath mainClass = "test.it.IT_RunAllCleanStartWsMain" workingDir = file('SHiNE-server') // пробрасываем системные флаги (по желанию) systemProperty "it.debug", System.getProperty("it.debug", "true") systemProperty "it.login", System.getProperty("it.login", "anya24") dependsOn testClasses } tasks.named('build') { finalizedBy tasks.named('integrationTest') } tasks.register('deployServerProduction', JavaExec) { group = "!!deployment" description = "Production deploy: build → upload to shineup.me → restart service (только после явного подтверждения)" classpath = sourceSets.test.runtimeClasspath mainClass = "test.it.IT_DeployRestartNoCleanNoTestsMain" workingDir = file('SHiNE-server') dependsOn shadowJar systemProperty "it.remoteHost", System.getProperty("it.remoteHost", "shineup.me") systemProperty "it.remoteUser", System.getProperty("it.remoteUser", "player") systemProperty "it.remoteDir", System.getProperty("it.remoteDir", "/home/player/SHiNE/shine-server") systemProperty "it.service", System.getProperty("it.service", "shine-server") systemProperty "it.localJar", System.getProperty("it.localJar", "build/libs/shine-server.jar") dependsOn testClasses } tasks.register('deployUIProduction', Exec) { group = "!!deployment" description = "Production UI deploy: shineup.me (только после явного подтверждения)" workingDir = rootDir commandLine 'bash', file('deploy_shine-PWA.sh').absolutePath } tasks.register('deployServer', Exec) { group = "!!deployment" description = "Default deploy server: t.shineup.me" dependsOn shadowJar workingDir = rootDir environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath commandLine 'bash', file('deploy_shine-server_test2.sh').absolutePath } tasks.register('deployUI', Exec) { group = "!!deployment" description = "Default deploy UI: t.shineup.me" workingDir = rootDir commandLine 'bash', file('deploy_shine-ui_test2.sh').absolutePath } tasks.register('deployServerTest2') { group = "!!deployment" description = "Явный алиас основного test deploy server: t.shineup.me" dependsOn tasks.named('deployServer') } tasks.register('deployUITest2') { group = "!!deployment" description = "Явный алиас основного test deploy UI: t.shineup.me" dependsOn tasks.named('deployUI') } tasks.register('deployServerTest', Exec) { group = "!!deployment" description = "Резервный test deploy: test.shineup.me (пока не использовать)" dependsOn shadowJar workingDir = rootDir environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath commandLine 'bash', file('deploy_shine-server_test.sh').absolutePath } tasks.register('deployUITest', Exec) { group = "!!deployment" description = "Резервный test UI deploy: test.shineup.me (пока не использовать)" workingDir = rootDir commandLine 'bash', file('deploy_shine-ui_test.sh').absolutePath } tasks.register('startLocal', Exec) { group = "!!run" description = "Builds server, starts local WS server and local HTTP UI for end-to-end local testing" dependsOn shadowJar dependsOn cleanServerLogs workingDir = rootDir def wsPort = System.getProperty("localWsPort", "7070") def webPort = System.getProperty("localWebPort", "8088") commandLine 'bash', '-lc', """ set -euo pipefail JAR_PATH="${file('SHiNE-server/build/libs/shine-server.jar').absolutePath}" UI_DIR="${file('shine-UI').absolutePath}" WS_PORT="${wsPort}" WEB_PORT="${webPort}" is_port_busy() { local port="\$1" if command -v ss >/dev/null 2>&1; then ss -ltnH "sport = :\$port" | grep -q . elif command -v lsof >/dev/null 2>&1; then lsof -iTCP:"\$port" -sTCP:LISTEN >/dev/null 2>&1 else return 1 fi } pick_free_port() { local p="\$1" while is_port_busy "\$p"; do p=\$((p + 1)) done echo "\$p" } WS_PORT="\$(pick_free_port "\$WS_PORT")" WEB_PORT="\$(pick_free_port "\$WEB_PORT")" echo "Starting SHiNE local stack..." echo "WS server port: \$WS_PORT" echo "UI HTTP port: \$WEB_PORT" echo "Client URL: http://localhost:\$WEB_PORT/?localWsPort=\$WS_PORT" ( cd "${file('SHiNE-server').absolutePath}" java -Dserver.port="\$WS_PORT" -jar "\$JAR_PATH" ) & SERVER_PID=\$! trap 'kill \$SERVER_PID 2>/dev/null || true' EXIT INT TERM CLIENT_URL="http://localhost:\$WEB_PORT/?localWsPort=\$WS_PORT" if command -v xdg-open >/dev/null 2>&1; then (xdg-open "\$CLIENT_URL" >/dev/null 2>&1 || true) & elif command -v open >/dev/null 2>&1; then (open "\$CLIENT_URL" >/dev/null 2>&1 || true) & elif command -v cmd.exe >/dev/null 2>&1; then (cmd.exe /c start "" "\$CLIENT_URL" >/dev/null 2>&1 || true) & else echo "Browser auto-open is not available on this host. Open manually: \$CLIENT_URL" fi SPA_SERVER_SCRIPT="${file('scripts/local_spa_server.py').absolutePath}" if command -v python3 >/dev/null 2>&1; then SHINE_UI_PORT="\$WEB_PORT" python3 "\$SPA_SERVER_SCRIPT" else SHINE_UI_PORT="\$WEB_PORT" python "\$SPA_SERVER_SCRIPT" fi """ } tasks.register('startLocalWithBuild') { group = "!!run" description = "Build server (build) and then start local stack (startLocal)" dependsOn tasks.named('build') dependsOn tasks.named('startLocal') } tasks.named('startLocal').configure { mustRunAfter tasks.named('build') }