191 lines
8.0 KiB
Java
191 lines
8.0 KiB
Java
package test.it;
|
||
|
||
import test.it.runner.IT_RunAllMain;
|
||
|
||
import java.io.BufferedReader;
|
||
import java.io.File;
|
||
import java.io.InputStreamReader;
|
||
import java.util.Enumeration;
|
||
import java.util.Objects;
|
||
import java.util.jar.JarEntry;
|
||
import java.util.jar.JarFile;
|
||
|
||
/**
|
||
* Деплой сервера с ПОЛНОЙ очисткой data, но только после:
|
||
* 1) явного интерактивного подтверждения;
|
||
* 2) бэкапа data в backup/data_YYYYMMDD-HHMMSS-SSS.
|
||
*
|
||
* После запуска сервиса выполняет полный IT-прогон.
|
||
*/
|
||
public class IT_DeployBackupCleanAndRunRemoteMain {
|
||
|
||
private static final String REMOTE_HOST = System.getProperty("it.remoteHost", "194.87.0.247");
|
||
private static final String REMOTE_USER = System.getProperty("it.remoteUser", "user");
|
||
|
||
private static final String REMOTE_DIR = System.getProperty("it.remoteDir", "/home/user/docker/shine-server");
|
||
private static final String REMOTE_JAR = REMOTE_DIR + "/shine-server.jar";
|
||
private static final String REMOTE_DATA = System.getProperty("it.remoteDataDir", REMOTE_DIR + "/data");
|
||
private static final String REMOTE_BACKUP_DIR = System.getProperty("it.remoteBackupDir", REMOTE_DIR + "/backup");
|
||
|
||
private static final String SERVICE_NAME = System.getProperty("it.service", "shine-server");
|
||
private static final String LOCAL_JAR = System.getProperty("it.localJar", "build/libs/shine-server.jar");
|
||
private static final String WS_URI_SERVER = System.getProperty("it.wsUri", "wss://shineup.me/ws");
|
||
|
||
public static void main(String[] args) {
|
||
requireDangerousConfirmation();
|
||
|
||
boolean serviceStopped = false;
|
||
boolean serviceStarted = false;
|
||
try {
|
||
sshStrict("sudo systemctl stop " + SERVICE_NAME + " || true");
|
||
serviceStopped = true;
|
||
|
||
validateLocalFatJarOrThrow(LOCAL_JAR);
|
||
sshStrict("mkdir -p " + q(REMOTE_DIR));
|
||
scpStrict(LOCAL_JAR, REMOTE_JAR + ".new");
|
||
verifyRemoteNewJarOrThrow(REMOTE_JAR + ".new");
|
||
sshStrict("mv -f " + q(REMOTE_JAR + ".new") + " " + q(REMOTE_JAR));
|
||
|
||
backupAndCleanRemoteDataOrThrow();
|
||
|
||
sshStrict("sudo systemctl start " + SERVICE_NAME);
|
||
serviceStarted = true;
|
||
waitRemotePort7070();
|
||
|
||
System.setProperty("it.wsUri", WS_URI_SERVER);
|
||
int failed = IT_RunAllMain.runAll();
|
||
System.exit(failed);
|
||
} catch (Throwable error) {
|
||
// На случай сбоя стараемся поднять сервис обратно.
|
||
if (serviceStopped && !serviceStarted) {
|
||
try {
|
||
sshStrict("sudo systemctl start " + SERVICE_NAME + " || true");
|
||
} catch (Throwable ignored) {
|
||
// ignore
|
||
}
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
private static void requireDangerousConfirmation() {
|
||
String warning = ""
|
||
+ "\n============================================================\n"
|
||
+ "ВНИМАНИЕ: будет очищена серверная БД после бэкапа.\n"
|
||
+ "HOST: " + REMOTE_USER + "@" + REMOTE_HOST + "\n"
|
||
+ "DATA: " + REMOTE_DATA + "\n"
|
||
+ "BACKUP DIR: " + REMOTE_BACKUP_DIR + "\n"
|
||
+ "Для продолжения введите: DELETE\n"
|
||
+ "============================================================\n";
|
||
System.out.print(warning);
|
||
try {
|
||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||
String answer = reader.readLine();
|
||
if (!"DELETE".equals(String.valueOf(answer).trim())) {
|
||
throw new RuntimeException("Операция отменена: подтверждение не получено.");
|
||
}
|
||
} catch (RuntimeException e) {
|
||
throw e;
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("Не удалось прочитать подтверждение из stdin", e);
|
||
}
|
||
}
|
||
|
||
private static void backupAndCleanRemoteDataOrThrow() {
|
||
String cmd = ""
|
||
+ "mkdir -p " + q(REMOTE_DATA) + " " + q(REMOTE_BACKUP_DIR) + " && "
|
||
+ "stamp=$(date +%Y%m%d-%H%M%S)-$(date +%3N) && "
|
||
+ "backup=" + q(REMOTE_BACKUP_DIR) + "/data_$stamp && "
|
||
+ "cp -a " + q(REMOTE_DATA) + " \"$backup\" && "
|
||
+ "echo backup_dir=$backup && "
|
||
+ "find " + q(REMOTE_DATA) + " -mindepth 1 -maxdepth 1 -exec rm -rf {} +";
|
||
sshStrict(cmd);
|
||
}
|
||
|
||
private static void waitRemotePort7070() {
|
||
for (int i = 0; i < 50; i++) {
|
||
int code = ssh("ss -ltnp | grep -q ':7070'");
|
||
if (code == 0) return;
|
||
sleepMs(200);
|
||
}
|
||
throw new RuntimeException("Remote port 7070 did not start in time on " + REMOTE_HOST);
|
||
}
|
||
|
||
private static void sshStrict(String remoteCmd) {
|
||
int code = ssh(remoteCmd);
|
||
if (code != 0) throw new RuntimeException("SSH command failed (" + code + "): " + remoteCmd);
|
||
}
|
||
|
||
private static int ssh(String remoteCmd) {
|
||
String cmd = "ssh " + REMOTE_USER + "@" + REMOTE_HOST + " " + q("bash -lc " + q(remoteCmd));
|
||
return sh(cmd);
|
||
}
|
||
|
||
private static void scpStrict(String local, String remote) {
|
||
Objects.requireNonNull(local);
|
||
Objects.requireNonNull(remote);
|
||
int code = sh("scp -p " + q(local) + " " + REMOTE_USER + "@" + REMOTE_HOST + ":" + q(remote));
|
||
if (code != 0) throw new RuntimeException("SCP failed (" + code + ")");
|
||
}
|
||
|
||
private static int sh(String cmd) {
|
||
try {
|
||
Process p = new ProcessBuilder("bash", "-lc", cmd).inheritIO().start();
|
||
return p.waitFor();
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("Command error: " + cmd, e);
|
||
}
|
||
}
|
||
|
||
private static String q(String s) {
|
||
return "'" + s.replace("'", "'\"'\"'") + "'";
|
||
}
|
||
|
||
private static void sleepMs(long ms) {
|
||
try {
|
||
Thread.sleep(ms);
|
||
} catch (InterruptedException e) {
|
||
Thread.currentThread().interrupt();
|
||
}
|
||
}
|
||
|
||
private static void validateLocalFatJarOrThrow(String localJarPath) {
|
||
File jar = new File(localJarPath);
|
||
if (!jar.isFile()) {
|
||
throw new RuntimeException("Local jar not found: " + localJarPath);
|
||
}
|
||
long size = jar.length();
|
||
if (size < 10L * 1024L * 1024L) {
|
||
throw new RuntimeException("Local jar is too small for fat-jar: " + size + " bytes (" + localJarPath + ")");
|
||
}
|
||
try (JarFile jf = new JarFile(jar)) {
|
||
boolean hasJetty = false;
|
||
boolean hasBc = false;
|
||
Enumeration<JarEntry> entries = jf.entries();
|
||
while (entries.hasMoreElements()) {
|
||
String name = entries.nextElement().getName();
|
||
if (!hasJetty && "org/eclipse/jetty/server/Handler.class".equals(name)) hasJetty = true;
|
||
if (!hasBc && "org/bouncycastle/jce/provider/BouncyCastleProvider.class".equals(name)) hasBc = true;
|
||
if (hasJetty && hasBc) break;
|
||
}
|
||
if (!hasJetty || !hasBc) {
|
||
throw new RuntimeException(
|
||
"Local jar doesn't look like fat-jar (missing deps). hasJetty=" + hasJetty + ", hasBC=" + hasBc
|
||
);
|
||
}
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("Failed to inspect local jar: " + localJarPath, e);
|
||
}
|
||
}
|
||
|
||
private static void verifyRemoteNewJarOrThrow(String remoteJarNewPath) {
|
||
String cmd = "test -f " + q(remoteJarNewPath) + " && " +
|
||
"sz=$(stat -c %s " + q(remoteJarNewPath) + ") && " +
|
||
"echo remote_new_size=$sz && test \"$sz\" -ge 10485760";
|
||
int code = ssh(cmd);
|
||
if (code != 0) {
|
||
throw new RuntimeException("Remote uploaded jar is missing or too small: " + remoteJarNewPath);
|
||
}
|
||
}
|
||
}
|