Installer workflow is done - Docker container is working fine

This commit is contained in:
2026-07-26 23:37:23 +05:30
parent 3cf5c83264
commit d684931bc5
1313 changed files with 778 additions and 408752 deletions

BIN
cygnus-installer/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -27,6 +27,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns-native-macos</artifactId>
<classifier>osx-aarch_64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>

View File

@@ -3,6 +3,7 @@ package com.cygnus.installer;
import java.util.UUID;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class ActivationClient {
@@ -16,20 +17,25 @@ public class ActivationClient {
}
public ActivationDtos.ValidationResponse validate(
String cloudServiceUrl,
String clientCode,
String licenseKey,
UUID installationUuid,
String installerVersion) {
return webClient.post()
.uri(settings.validationPath())
.uri(endpoint(cloudServiceUrl, settings.validationPath()))
.bodyValue(new ActivationDtos.ValidationRequest(
clientCode, licenseKey, installationUuid, installerVersion))
.retrieve()
.onStatus(
status -> status.isError(),
response -> cloudFailure(response.statusCode().value(), response))
.bodyToMono(ActivationDtos.ValidationResponse.class)
.block();
}
public ActivationDtos.RegistrationResponse register(
String cloudServiceUrl,
ActivationDtos.ValidationResponse validation,
String installationCode,
String installationName,
@@ -37,7 +43,7 @@ public class ActivationClient {
String softwareVersion,
String environment) {
return webClient.post()
.uri(settings.registrationPath())
.uri(endpoint(cloudServiceUrl, settings.registrationPath()))
.bodyValue(new ActivationDtos.RegistrationRequest(
validation.activationToken(),
installationCode,
@@ -46,7 +52,25 @@ public class ActivationClient {
softwareVersion,
environment))
.retrieve()
.onStatus(
status -> status.isError(),
response -> cloudFailure(response.statusCode().value(), response))
.bodyToMono(ActivationDtos.RegistrationResponse.class)
.block();
}
private String endpoint(String cloudServiceUrl, String path) {
String base = cloudServiceUrl.trim();
return (base.endsWith("/") ? base.substring(0, base.length() - 1) : base)
+ path;
}
private Mono<? extends Throwable> cloudFailure(
int status,
org.springframework.web.reactive.function.client.ClientResponse response) {
return response.bodyToMono(String.class)
.defaultIfEmpty("No response details were provided")
.map(body -> new IllegalStateException(
"Cloud service returned HTTP " + status + ": " + body));
}
}

View File

@@ -0,0 +1,71 @@
package com.cygnus.installer;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class DockerComposeDeploymentService {
private static final Duration PULL_TIMEOUT = Duration.ofMinutes(5);
private static final Duration START_TIMEOUT = Duration.ofMinutes(2);
private static final Duration VERIFY_TIMEOUT = Duration.ofSeconds(30);
private final CommandExecutor executor;
public DockerComposeDeploymentService(CommandExecutor executor) {
this.executor = executor;
}
public void pull(Path deploymentDirectory) {
require(
command(deploymentDirectory, "pull"),
PULL_TIMEOUT,
"Could not pull the service image");
}
public void start(Path deploymentDirectory) {
require(
command(deploymentDirectory, "up", "-d", "--remove-orphans"),
START_TIMEOUT,
"Could not start the service");
}
public void verifyRunning(Path deploymentDirectory, String serviceName) {
CommandResult result = executor.execute(
command(deploymentDirectory, "ps", "--status", "running", "--services"),
VERIFY_TIMEOUT);
boolean running = result.successful()
&& result.output().lines().anyMatch(serviceName::equals);
if (!running) {
throw new IllegalStateException(
"The service container did not reach running state. "
+ result.output());
}
}
private List<String> command(Path directory, String... arguments) {
Path normalized = directory.toAbsolutePath().normalize();
java.util.ArrayList<String> command = new java.util.ArrayList<>(List.of(
"docker",
"compose",
"--project-directory",
normalized.toString(),
"--env-file",
normalized.resolve(".env").toString(),
"-f",
normalized.resolve("compose.yml").toString()));
command.addAll(List.of(arguments));
return List.copyOf(command);
}
private void require(
List<String> command, Duration timeout, String failureMessage) {
CommandResult result = executor.execute(command, timeout);
if (!result.successful()) {
throw new IllegalStateException(
failureMessage + ". " + result.output());
}
}
}

View File

@@ -23,6 +23,8 @@ public class IniConfigurationLoader {
"Unsupported product '" + product + "'. Expected matrix or cygnus");
}
URI cloudUri = absoluteHttpUri(ini.required("installer", "cloud_service_url"));
URI containerCloudUri = absoluteHttpUri(ini.optional(
"installer", "container_cloud_service_url", cloudUri.toString()));
String apiBasePath = normalizedApiPath(
ini.required("installer", "installer_api_base_path"));
String environment = ini.required("installer", "environment");
@@ -31,6 +33,7 @@ public class IniConfigurationLoader {
return new InstallerSettings(
product,
cloudUri,
containerCloudUri,
apiBasePath,
environment,
Path.of(output),

View File

@@ -7,4 +7,7 @@ public record InstallationResult(
UUID installationId,
String installationCode,
String clientId,
Path outputDirectory) {}
Path outputDirectory,
String serviceName,
String serviceUrl,
String serviceStatus) {}

View File

@@ -27,9 +27,7 @@ public class InstallerConfiguration {
}
@Bean
WebClient installerWebClient(InstallerSettings settings) {
return WebClient.builder()
.baseUrl(settings.cloudServiceUrl().toString())
.build();
WebClient installerWebClient() {
return WebClient.builder().build();
}
}

View File

@@ -6,6 +6,7 @@ import java.nio.file.Path;
public record InstallerSettings(
String product,
URI cloudServiceUrl,
URI containerCloudServiceUrl,
String installerApiBasePath,
String environment,
Path defaultOutputDirectory,

View File

@@ -0,0 +1,13 @@
package com.cygnus.installer;
public record RuntimeConfiguration(
String containerCloudServiceUrl,
String databaseUrl,
String databaseUsername,
String databasePassword,
String redisHost,
String redisPort,
String redisPassword,
boolean redisSsl,
String cloudRequestTimeout,
String tokenRefreshSkew) {}

View File

@@ -26,6 +26,9 @@ public class TechnobeeServiceInstallerApplication implements CommandLineRunner {
SpringApplication application =
new SpringApplication(TechnobeeServiceInstallerApplication.class);
application.setWebApplicationType(WebApplicationType.NONE);
// Spring Boot defaults to headless mode, which would force the installer
// into its CLI fallback even on desktops that support Swing.
application.setHeadless(false);
application.setLogStartupInfo(false);
try {
ConfigurableApplicationContext context = application.run(args);

View File

@@ -21,6 +21,7 @@ class IniConfigurationLoaderTest {
[installer]
product=matrix
cloud_service_url=https://cloud.example.com/
container_cloud_service_url=http://host.docker.internal:8090/
installer_api_base_path=/api/v1/installations/
environment=production
""");
@@ -30,6 +31,8 @@ class IniConfigurationLoaderTest {
assertThat(settings.product()).isEqualTo("matrix");
assertThat(settings.cloudServiceUrl().toString())
.isEqualTo("https://cloud.example.com");
assertThat(settings.containerCloudServiceUrl().toString())
.isEqualTo("http://host.docker.internal:8090");
assertThat(settings.validationPath())
.isEqualTo("/api/v1/installations/activation/validate");
assertThat(settings.registrationPath())
@@ -38,6 +41,22 @@ class IniConfigurationLoaderTest {
.isEqualTo(Path.of("./matrix-installation"));
}
@Test
void defaultsContainerCloudUrlToCanonicalCloudUrl() throws Exception {
Path ini = write("""
[installer]
product=matrix
cloud_service_url=https://cloud.example.com/
installer_api_base_path=/api/v1/installations
environment=production
""");
InstallerSettings settings = loader.load(ini, "1.2.3");
assertThat(settings.containerCloudServiceUrl())
.isEqualTo(settings.cloudServiceUrl());
}
@Test
void rejectsUnknownProduct() throws Exception {
Path ini = write("""

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.cygnus.installer.DockerComposeDeploymentServiceTest
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in com.cygnus.installer.DockerComposeDeploymentServiceTest