Projekt: Installer z svn, mvn i spring-boot

Każda aplikacja warta lub nie warta uwagi ale posiadająca aspiracje ma dziś własny instalator. W dzisiejszym odcinku zajmiemy się stworzenie narzędzia do instalacji naszego projektu. Będzie to plik wykonywalny typu EXE. Jego zadaniem będzie zebraniem informacji od użytkownika o przeczytaniu licencji (zaznaczeniu odpowiedniego kwadracika), zadecydowanie o utworzeniu ikony na pulpicie, wybranie miejsca na dysku gdzie ma zostać zainstalowana aplikacja (plik jar), oraz na samym końcu uruchomienie naszej aplikacji.
Do tej prezentacji wybrałem bardzo prostą opcję czyli zagnieździłem plik .jar w pliku .exe, wiem że dziś mamy dobrodziejstwo internetu i możemy sobie spokojnie pobrać zasoby. Sypię głowę popiołem ale naszym celem jest uruchomienie aplikacji a nie sprzeczanie się nad tym co można 😉
Instalator jest bardzo prosty i to jest jego celem. Jeśli ktoś ma chęć rozszerzyć projekt o nowe możliwości to proszę bardzo, będzie mi miło że dałem komuś natchnienie.

Zapraszam do lektury i komentarzy!

Zacznijmy od wyboru technologii

L.P. Co Więcej
1 repo* http://git.e-strix.com/sample_installer.git/
2 Java 1.8.0
3 Maven 3.3.9

*) W konfiguracji projektu (plik pom.xml) jest oznaczony svn, natomiast aktualnie repo znajduje się na prywatnym serwerze git. Powodem jest fakt że nie opłaca stawiać mi się stawiać svn’a dla jednego projektu. W załączonym filmie będę pracował na SVN i myślę że to wystarczy. Jeśli jesteś zainteresowany konfiguracją SVN zapraszam tutaj

1. Zaczynajmy: Struktura projektu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
│   LICENSE.md
│   pom.xml
│   README.md

└───src
  └───main
    ├───java
    │  └───pl
    │    └───estrix
    │      └───javafx
    │        AppContext.java
    │        FilterProp.java
    │        InstallerService.java
    │        JavaApplication.java
    │        MainController.java
    │        Step1Controller.java
    │        Step2Controller.java
    │        Step3Controller.java
    │
    └───resources
      │    main.fxml
      │    step_1.fxml
      │    step_2.fxml
      │    step_3.fxml
      │
      ├───content
      │    application.ico
      │    config.bat
      │    JavaFxGitSample_5.0.0.jar
      │    sudo.cmd
      │
      ├───img
      │    application.png
      │    logo.png
      │
      └───META-INF
           spring.factories

Nie będę omawiał każdego pliku, nie ma na to czasu, zwrócę uwagę tylko na te, które wnoszą coś ciekawego. Są to dość proste rzeczy, jeśli masz wątpliwości to proszę wróć do podstaw programowania w java i javaFx. Jest wiele dobrych materiałów na YouTube o tym temacie, również w języku polskim. Mój ulubiony to kanał „Zacznij Programować”, polecam do zapoznania się 🙂

Cały projekt można pobrać z odnośnika repozytorium.

Plik: pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?xml version="1.0" encoding="ISO-8859-2"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.estrix</groupId>
  <artifactId>estrix-jar-launcher</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>launcher</name>
  <url>http://maven.apache.org</url>

  <profiles>
    <profile>
      <id>windows-deploy</id>
      <activation>
        <os>
          <family>Windows</family>
        </os>
      </activation>
      <build>
        <plugins>
          <plugin>
            <groupId>com.akathist.maven.plugins.launch4j</groupId>
            <artifactId>launch4j-maven-plugin</artifactId>
            <version>1.7.22</version>
            <executions>
              <execution>
                <id>l4j-clui</id>
                <phase>package</phase>
                <goals>
                  <goal>launch4j</goal>
                </goals>
                <configuration>
                  <headerType>gui</headerType>
                  <outfile>target/JavaFxGitSample-win-install.exe</outfile>
                  <jar>target/estrix-jar-launcher-1.0-SNAPSHOT.jar</jar>
                  <classPath>
                    <mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
                    <preCp>anything</preCp>
                  </classPath>

                  <icon>src/main/resources/content/application.ico</icon>
                  <jre>
                    <minVersion>1.8.0</minVersion>
                    <jdkPreference>preferJre</jdkPreference>
                  </jre>

                  <versionInfo>
                    <fileVersion>1.0.0.0</fileVersion>
                    <txtFileVersion>${project.version}</txtFileVersion>
                    <fileDescription>${project.name}</fileDescription>
                    <copyright>2018 e-Strix.com</copyright>
                    <productVersion>1.0.0.0</productVersion>
                    <txtProductVersion>1.0.0.0</txtProductVersion>
                    <productName>${project.name}</productName>
                    <companyName>e-Strix Kamil Mucik</companyName>
                    <internalName>e-Strix Kamil Mucik</internalName>
                    <originalFilename>JavaFxGitSample-win-install.exe</originalFilename>
                  </versionInfo>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>

  <scm>
    <connection>scm:svn:http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk</connection>
    <developerConnection>scm:svn:http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk</developerConnection>
    <url>http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk</url>
  </scm>

  <distributionManagement>
    <repository>
      <uniqueVersion>false</uniqueVersion>
      <id>corp1</id>
      <name>Corporate Repository</name>
      <url>file://C:\temp</url>
      <!--<url>scp://repo/maven2</url>-->
      <layout>default</layout>
    </repository>
  </distributionManagement>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring.boot.mainClass>pl.estrix.javafx.JavaApplication</spring.boot.mainClass>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-io</artifactId>
      <version>1.3.2</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <executable>true</executable>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <phase>generate-resources</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <tasks>
                <mkdir dir="${project.build.directory}" />
                <tstamp>
                  <format property="last.updated" pattern="yyyy.MM.dd HH:mm" />
                </tstamp>
                <echo file="${basedir}/target/classes/filter.properties" append="false" message="estrix.application.biuld-time=${last.updated}" />
                <echo file="${basedir}/target/classes/filter.properties" append="true" message="${line.separator}" />
                <echo file="${basedir}/target/classes/filter.properties" append="true" message="estrix.application.name=${project.name}" />
                <echo file="${basedir}/target/classes/filter.properties" append="true" message="${line.separator}" />
                <echo file="${basedir}/target/classes/filter.properties" append="true" message="estrix.application.version=${project.version}" />
              </tasks>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Plik: src\main\java\pl\estrix\javafx\AppContext.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package pl.estrix.javafx;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.stage.Stage;
import org.springframework.context.ConfigurableApplicationContext;

class AppContext {

    /**
     * Path where app will be installed.
     */

    static String targetPath = "C:\\Users\\TEST\\AppData\\Local\";

    /**
     * No comment.
     */
    static Boolean createShortcut = false;

    static ConfigurableApplicationContext springContext;

    static Boolean finish = Boolean.FALSE;

    static Stage primaryStage;

    static BooleanProperty licenseAgreement = new SimpleBooleanProperty(Boolean.FALSE);
    static BooleanProperty buttonNextProperty = new SimpleBooleanProperty(Boolean.FALSE);
    static BooleanProperty buttonPrevProperty = new SimpleBooleanProperty(Boolean.FALSE);


}

Plik: src\main\java\pl\estrix\javafx\FilterProp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package pl.estrix.javafx;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties("filter.properties")
class FilterProp {

    @Value("${estrix.application.name}")
    private String name ;
    @Value("${estrix.application.version}")
    private String version ;
    @Value("${estrix.application.biuld-time}")
    private String biuldTime ;

    String getName() {
        return name;
    }

    String getVersion() {
        return version;
    }

    String getBiuldTime() {
        return biuldTime;
    }

}

Plik: src\main\java\pl\estrix\javafx\InstallerService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package pl.estrix.javafx;

import org.apache.commons.io.FileUtils;

import java.io.*;
import java.net.URL;

class InstallerService {

    static void createFolder(String path) {
        try {
            path += "_SampleApp/";
            boolean success = (new File(path)).mkdirs();

            if (success) {
                AppContext.targetPath = path;
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    static void createSettings(){
        try {
            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(AppContext.targetPath + "settings.bat", true)));
            out.println("set app_path="+AppContext.targetPath);
            out.println("set app_desktop_shortcut="+AppContext.createShortcut);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void copyResource(String file){
        URL inputUrl = InstallerService.class.getResource("/content/" + file);
        File dest = new File(AppContext.targetPath + "/" + file);
        try {
            FileUtils.copyURLToFile(inputUrl, dest);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void executeBatch(){
        try {
            Runtime.getRuntime().exec("cmd /c config.bat", null, new File(AppContext.targetPath));
        } catch (IOException  e) {
            e.printStackTrace();
        }
    }

    static void launchApp(){
        try {
            Runtime.getRuntime().exec("javaw.exe -jar JavaFxGitSample_5.0.0.jar", null, new File(AppContext.targetPath));
        } catch (IOException  e) {
            e.printStackTrace();
        }
    }
}

Plik: src\main\java\pl\estrix\javafx\JavaApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package pl.estrix.javafx;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.*;

@SpringBootApplication
@ComponentScan(basePackages = "pl.estrix.javafx")
@PropertySource("classpath:filter.properties")
@Configuration
public class JavaApplication extends Application {

    private ConfigurableApplicationContext context;
    private Parent rootNode;

    public static void main(final String[] args) {
        Application.launch(args);
    }

    @Override
    public void init() throws Exception {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(JavaApplication.class);
        context = builder.run(getParameters().getRaw().toArray(new String[0]));

        AppContext.springContext = context;
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"));
        loader.setControllerFactory(AppContext.springContext::getBean);
        rootNode = loader.load();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(rootNode, 580, 420));
        primaryStage.centerOnScreen();
        primaryStage.show();

        Image icon = new Image(JavaApplication.class.getResourceAsStream("/img/application.png"));
        primaryStage.getIcons().add(icon);

        AppContext.primaryStage = primaryStage;
    }

    @Override
    public void stop() throws Exception {
        AppContext.springContext.close();
        Platform.exit();
    }

}

Plik: src\main\java\pl\estrix\javafx\MainController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import java.io.IOException;

@Controller
public class MainController {

    @Autowired
    private FilterProp filterProp;
    @Autowired
    private JavaApplication javaApplication;

    @FXML
    private BorderPane borderPane;
    @FXML
    private Button nextButton;
    @FXML
    private Button prevButton;
    @FXML
    private Label infoBuild;
    @FXML
    private Label infoName;
    @FXML
    private Label infoVersion;

    @FXML
    public void initialize() {
        nextButton.disableProperty().bind(AppContext.buttonNextProperty.not());
        prevButton.disableProperty().bind(AppContext.buttonPrevProperty.not());

        infoBuild.setText(filterProp.getBiuldTime());
        infoName.setText(filterProp.getName());
        infoVersion.setText(filterProp.getVersion());

        step1();
    }

    private void setCenter(String fxmlPath)  {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlPath));
            loader.setControllerFactory(AppContext.springContext::getBean);
            AnchorPane node = loader.load();

            borderPane.setCenter( node);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void step1() {setCenter("/step_1.fxml"); }

    private void step2() {setCenter("/step_2.fxml"); }

    void step3() {setCenter("/step_3.fxml"); }

    public void onPrevAction() {
        step1();
    }

    public void onNextAction() {
        step2();
    }

    void onCloseAction() {
        try {
            javaApplication.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Plik: src\main\java\pl\estrix\javafx\Step1Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.scene.control.RadioButton;
import org.springframework.stereotype.Controller;

@Controller
public class Step1Controller {

    @FXML
    private RadioButton agreementCheckYes;

    @FXML
    public void initialize() {
        agreementCheckYes.selectedProperty().addListener( (object, oldValue, newValue) ->{
            AppContext.licenseAgreement.setValue(newValue);
            AppContext.buttonNextProperty.setValue(newValue);
        });

        AppContext.buttonPrevProperty.setValue(Boolean.FALSE);
        agreementCheckYes.selectedProperty().setValue(AppContext.licenseAgreement.getValue());
    }
}

Plik: src\main\java\pl\estrix\javafx\Step2Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package pl.estrix.javafx;

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import java.io.File;

@Controller
public class Step2Controller {

    @Autowired
    private MainController mainController;

    @FXML
    private TextField textField2;
    @FXML
    private ProgressBar progressBar;
    @FXML
    private CheckBox createShortcut;
    @FXML
    private Button installButton;
    @FXML
    private Button changeDirButton;

    @FXML
    public void initialize() {
        AppContext.buttonPrevProperty.setValue(Boolean.TRUE);
        AppContext.buttonNextProperty.setValue(Boolean.FALSE);

        textField2.setText(AppContext.targetPath);

        createShortcut.selectedProperty().addListener( ((observable, oldValue, newValue) -> {
            AppContext.createShortcut = newValue;
        }));

        Platform.runLater( () -> {
            installButton.requestFocus();
        });
    }

    public void onPathChangeAction() {
        DirectoryChooser chooser = new DirectoryChooser();
        chooser.setTitle("Wybierz folder aplikacji");
        File defaultDirectory = new File(AppContext.targetPath);
        chooser.setInitialDirectory(defaultDirectory);
        File selectedDirectory = chooser.showDialog(AppContext.primaryStage);
        if (selectedDirectory != null) {
            AppContext.targetPath = selectedDirectory.getAbsolutePath();
        } else {
            AppContext.targetPath = textField2.getPromptText();
        }
        textField2.setText(AppContext.targetPath);
    }

    public void onInstallAction() {
        Task copyWorker = createWorker();
        progressBar.progressProperty().unbind();
        progressBar.progressProperty().bind(copyWorker.progressProperty());
        installButton.disableProperty().setValue(Boolean.TRUE);
        changeDirButton.disableProperty().setValue(Boolean.TRUE);
        createShortcut.disableProperty().setValue(Boolean.TRUE);

        copyWorker.messageProperty().addListener((observable, oldValue, newValue) ->
                System.out.println(newValue)
        );
        copyWorker.stateProperty().addListener((observable, oldValue, newValue) -> {
            if (Worker.State.SUCCEEDED.equals(newValue)){
                AppContext.finish = true;
                AppContext.buttonNextProperty.setValue(Boolean.TRUE);
                AppContext.buttonPrevProperty.setValue(Boolean.FALSE);
                mainController.step3();
            }
        });

        new Thread(copyWorker).start();
    }


    private Task createWorker() {
        return new Task() {
            @Override
            protected Object call() throws Exception {
                updateProgress(1,10);
                InstallerService.createFolder(AppContext.targetPath);
                updateProgress(2,10);
                InstallerService.createSettings();
                updateProgress(3,10);
                InstallerService.copyResource("sudo.cmd");
                updateProgress(4,10);
                InstallerService.copyResource("JavaFxGitSample_5.0.0.jar");
                updateProgress(5,10);
                InstallerService.copyResource("application.ico");
                updateProgress(6,10);
                InstallerService.copyResource("config.bat");
                updateProgress(7,10);
                InstallerService.executeBatch();

                updateProgress(10,10);
                return true;
            }
        };
    }

}

Plik: src\main\java\pl\estrix\javafx\Step3Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class Step3Controller {

    @Autowired
    private MainController mainController;

    @FXML
    private CheckBox launchApp;

    @FXML
    public void initialize() {
        AppContext.buttonPrevProperty.setValue(Boolean.FALSE);
        AppContext.buttonNextProperty.setValue(Boolean.FALSE);
    }

    public void onExitAppAction() {
        if (launchApp.isSelected()) {
            InstallerService.launchApp();
        }

        mainController.onCloseAction();
    }

}

Plik: src\main\resources\main.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.Cursor?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="420.0" prefWidth="580.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.MainController">
   <children>
      <VBox prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
         <children>
            <HBox alignment="CENTER" style="-fx-background-color: #ffffff;">
               <children>
                  <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="380.0" prefWidth="160.0" HBox.hgrow="ALWAYS">
                     <children>
                        <ImageView fitHeight="380.0" fitWidth="160.0" pickOnBounds="true" preserveRatio="true" AnchorPane.leftAnchor="0.0">
                           <image>
                              <Image url="@img/logo.png" />
                           </image>
                        </ImageView>
                        <Label fx:id="infoVersion" layoutX="35.0" layoutY="318.0" text="Label" AnchorPane.bottomAnchor="4.0" AnchorPane.leftAnchor="8.0" />
                        <Label fx:id="infoName" layoutY="327.0" text="Label" AnchorPane.bottomAnchor="24.0" AnchorPane.leftAnchor="8.0" />
                        <Label fx:id="infoBuild" layoutX="10.0" layoutY="337.0" text="Label" AnchorPane.bottomAnchor="44.0" AnchorPane.leftAnchor="8.0" />
                     </children>
                  </AnchorPane>
                  <VBox maxHeight="1.7976931348623157E308" prefWidth="217.0" style="-fx-background-color: #00cd6b;" HBox.hgrow="ALWAYS">
                     <children>
                        <Label alignment="CENTER" contentDisplay="CENTER" maxWidth="1.7976931348623157E308" text="e-Strix.com" textAlignment="CENTER" textFill="WHITE">
                           <VBox.margin>
                              <Insets bottom="10.0" top="10.0" />
                           </VBox.margin>
                        </Label>
                        <AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-background-color: #f0f0f0;" VBox.vgrow="ALWAYS">
                           <children>
                              <BorderPane fx:id="borderPane" layoutX="106.0" layoutY="61.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
                           </children>
                        </AnchorPane>
                     </children>
                  </VBox>
               </children>
               <cursor>
                  <Cursor fx:constant="DEFAULT" />
               </cursor>
            </HBox>
            <AnchorPane maxWidth="1.7976931348623157E308" prefHeight="200.0">
               <children>
                  <Button fx:id="prevButton" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" onAction="#onPrevAction" prefWidth="100.0" text="Wstecz" AnchorPane.leftAnchor="178.0" AnchorPane.topAnchor="8.0" />
                  <Button fx:id="nextButton" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" onAction="#onNextAction" prefWidth="100.0" text="Dalej" textAlignment="CENTER" AnchorPane.rightAnchor="18.0" AnchorPane.topAnchor="8.0" />
               </children>
            </AnchorPane>
         </children>
      </VBox>
   </children>
</AnchorPane>

Plik: src\main\resources\step_1.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="340.0" prefWidth="420.0" style="-fx-background-color: #f0f0f0;" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.Step1Controller">
   <children>
      <TextArea layoutX="11.0" layoutY="14.0" promptText="Licencja" AnchorPane.bottomAnchor="50.0" AnchorPane.leftAnchor="8.0" AnchorPane.rightAnchor="8.0" AnchorPane.topAnchor="8.0" />
      <RadioButton layoutX="14.0" layoutY="281.0" mnemonicParsing="false" selected="true" text="Nie zgadzam się" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="8.0">
         <toggleGroup>
            <ToggleGroup fx:id="accept" />
         </toggleGroup>
      </RadioButton>
      <RadioButton fx:id="agreementCheckYes" layoutX="14.0" layoutY="309.0" mnemonicParsing="false" text="Zgadzam się" toggleGroup="$accept" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="8.0" />
   </children>
</AnchorPane>

Plik: src\main\resources\step_2.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="340.0" prefWidth="420.0" style="-fx-background-color: #f0f0f0;" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.Step2Controller">
   <children>
      <Label layoutX="43.0" layoutY="39.0" text="step 2" AnchorPane.leftAnchor="8.0" AnchorPane.topAnchor="24.0" />
      <TextField fx:id="textField2" disable="true" editable="false" layoutX="43.0" layoutY="70.0" prefHeight="25.0" prefWidth="284.0" promptText="C:\Program Files\JavaFxSample" AnchorPane.leftAnchor="8.0" AnchorPane.rightAnchor="64.0" AnchorPane.topAnchor="48.0" />
      <ProgressBar fx:id="progressBar" layoutX="8.0" layoutY="300.0" prefHeight="26.0" prefWidth="200.0" progress="0.0" AnchorPane.bottomAnchor="22.0" AnchorPane.leftAnchor="8.0" AnchorPane.rightAnchor="64.0" />
      <Separator layoutY="273.0" prefWidth="200.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" />
      <Button fx:id="installButton" layoutX="344.0" layoutY="292.0" mnemonicParsing="false" onAction="#onInstallAction" text="Instaluj" AnchorPane.rightAnchor="8.0" />
      <CheckBox fx:id="createShortcut" layoutX="40.0" layoutY="114.0" mnemonicParsing="false" text="Utwórz ikonę na pulpicie" AnchorPane.leftAnchor="8.0" AnchorPane.topAnchor="84.0" />
      <Button fx:id="changeDirButton" layoutX="345.0" layoutY="70.0" mnemonicParsing="false" onAction="#onPathChangeAction" text="Zmień" AnchorPane.rightAnchor="8.0" AnchorPane.topAnchor="48.0" />
   </children>
</AnchorPane>

Plik: src\main\resources\step_3.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="340.0" prefWidth="420.0" style="-fx-background-color: #f0f0f0;" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.Step3Controller">
    <children>
        <Label layoutX="61.0" layoutY="35.0" text="Podsumowanie" AnchorPane.leftAnchor="24.0" AnchorPane.topAnchor="24.0" />
      <Button layoutX="120.0" layoutY="155.0" mnemonicParsing="false" onAction="#onExitAppAction" prefHeight="30.0" prefWidth="180.0" text="Zakończ" />
      <CheckBox fx:id="launchApp" layoutX="44.0" layoutY="170.0" mnemonicParsing="false" text="Uruchom aplikacje" AnchorPane.leftAnchor="24.0" AnchorPane.topAnchor="64.0" />
    </children>
</AnchorPane>

Plik: src\main\resources\content\config.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@echo off

:configloader
call settings.bat
:configloaderends

set mypath=%cd%
set binPath=%mypath%\bin
if not exist "%binPath%" (
    mkdir  %binPath%
)

rem :::::::: CREATE Menu Start Icon
echo Set oWS = WScript.CreateObject("WScript.Shell") > %mypath%\CreateShortcut.vbs
echo sLinkFile = "%mypath%\bin\JavaFxGitSample.lnk" >> %mypath%\CreateShortcut.vbs
echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %mypath%\CreateShortcut.vbs
echo oLink.TargetPath = "%mypath%\JavaFxGitSample_5.0.0.jar" >> %mypath%\CreateShortcut.vbs
echo oLink.WorkingDirectory = "%mypath%" >> %mypath%\CreateShortcut.vbs
echo oLink.Description = "JavaFxGitSample" >> %mypath%\CreateShortcut.vbs
echo oLink.IconLocation = "%mypath%\application.ico" >> %mypath%\CreateShortcut.vbs
echo oLink.Save >> CreateShortcut.vbs
cscript CreateShortcut.vbs
del CreateShortcut.vbs

if "%app_desktop_shortcut%" == "true" (
    copy %mypath%\bin\JavaFxGitSample.lnk %userprofile%\Desktop\JavaFxGitSample.lnk
)

sudo.cmd  xcopy  %mypath%\bin %ProgramData%\Microsoft\Windows""Start Menu""\Programs\e-Strix\

Plik: src\main\resources\content\sudo.cmd

1
2
3
4
@echo Set objShell = CreateObject("Shell.Application") > %temp%\sudo.tmp.vbs
@echo args = Right("%*", (Len("%*") - Len("%1"))) >> %temp%\sudo.tmp.vbs
@echo objShell.ShellExecute "%1", args, "", "runas" >> %temp%\sudo.tmp.vbs
@cscript %temp%\sudo.tmp.vbs

Plik: src\main\resources\META-INF\spring.factories

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
pl.estrix.javafx.JavaApplication

Ciągła integracja

Wstęp

Normalnie żaden projekt informatyczny nie jest budowany przez wydelegowaną osobę, nie miało by to większego sensu, ale chętnie na taki etat bym aplikował. No ale niestety dzisiejsze system ciągłej integracji zabierają naprawdę fajną pracę.

Tak czy inaczej dziś przedstawiam jak uruchomić Jenkins’a, jako że opensource ma się dobrze to wybieram tę wersję. Oczywiście nie narzucam wybieranie tego konkretnego rozwiązania.  Równie dobrze możesz wybrać Travis’a, TeamCity, Hudson’a czy którykolwiek z systemów jaki uważasz. Ja wybieram Jenkins’a ponieważ każda firma z którą pracowałem miała go na pokładzie.

Zatem zacznijmy od początku, czyli Java:

  1. Java

    Pierwszym krokiem będzie dodanie repozytorium do naszego Ubuntu i pobranie z niego zawartości:

    add-apt-repository ppa:webupd8team/java
    apt-get update

    Teraz spokojnie możemy instalujemy:

    apt-get install oracle-java8-installer

    Po całym procesie nasza java powinna wskazywać poprawną wartość, czytaj 1.8.x. Zatem zapytajmy się jaką wersje właśnie zainstalowaliśmy:

    java -version

    i/lub kompilator:

    javac -version

    Dla pewności, możemy upewnić się że nie będzie zgrzytów na etapie zależności. Więc sprawdzamy alternatywy:

    update-alternatives --config java

    Wybieramy naszą instalację i wszystko teraz będzie działać. W kolejnym kroku dodamy naszą instalację do zmiennych środowiskowych:

    vim /etc/environment

    Dodaj na początku klucz=wartość na początku pliku:

    JAVA_HOME="/usr/lib/jvm/java-8-oracle"

    I aktualizujemy zmiany poprzez komendę source:

    source /etc/environment 
    

    Teraz już jest definitywny koniec działań z javą. Sprawdzamy czy działa zmienna środowiskowa:

    echo $JAVA_HOME

    Działa, więc zainstalujmy robotników od czarnej roboty:

  2. Maven

    Maven, solidny framework oparty na pluginach, buduje aplikacje dla banków, ubezpieczeń, portale i inne bardzo ważne rzeczy.
    Zatem pobieramy i rozpakowujemy:

    Notatka: Tę akcję wykonuje w katalogu „/tmp”, ale nie ma takie obowiązku. Pamiętaj że jesteś na prawach „root”, więc to zobowiązuje do czegoś.

    wget http://ftp.man.poznan.pl/apache/maven/maven-3/3.5.3/binaries/apache-maven-3.5.3-bin.tar.gz
    
    tar zxvf apache-maven-3.5.3-bin.tar.gz

    Teraz przenieśmy do katalogu /opt/:

    mv apache-maven-3.5.3/ /opt/

    I dodajemy zmienne środowiskowe, podobnie jak miało to miejsce dla pkt.1.
    Otwieramy plik środowisko:

     vim /etc/environment

    i dodajemy naszą zmienną:

     M2_HOME="/opt/apache-maven-3.5.3"

    Teraz jeszcze na końcu zmiennej PATH należy dodać odwołanie do katalogu bin:

    :$M2_HOME/bin

    Teraz aktualizujemy ponownie środowisko:

    source /etc/environment

    i sprawdzamy co się wydarzyło:

    # mvn -v 
    Apache Maven 3.5.3 (3383c37e1f9e9b3bc3df5050c29c8aff9f295297; 2018-02-24T20:49:05+01:00) 
    Maven home: /opt/apache-maven-3.5.3 
    Java version: 1.8.0_161, vendor: Oracle Corporation Java home: /usr/lib/jvm/java-8-oracle/jre 
    Default locale: en_US, platform encoding: UTF-8 
    OS name: "linux", version: "4.4.0-92-generic", arch: "amd64", family: "unix"
  3. Gradle

    Kolejnym specjalistom do spraw budowy jest Gradle, projekt bardzo szybko się rozwija i niesie ze sobą duży potencjał. Pierwsza różnica jaka jest widoczna w stosunku do Mavena, to plik ustawień bazuje na formacie skryptów Groove, gdy poprzednika opisywaliśmy w XMLach.

    A zatem do dzieła. Pobieramy i rozpakowujemy:

    wget https://services.gradle.org/distributions/gradle-4.6-bin.zip
    unzip gradle-4.6-bin.zip

    Teraz przeniesiemy do katalogu /opt/, tak jak poprzednika:

    mv gradle-4.6/ /opt/

    I zmienne środowiskowe:

    vim /etc/environment

    Dodajemy wpis:

    GRADLE_HOME="/opt/gradle-4.6"

    i do zmiennej PATH, dodajemy odwołanie do katalogu bin:

    $GRADLE_HOME/bin

    Teraz aktualizacja środowiska:

    source /etc/environment

    i sprawdzamy efekt:

    # gradle -v 
    ------------------------------------------------------------ 
    Gradle 4.6 
    ------------------------------------------------------------ 
    Build time: 2018-02-28 13:36:36 UTC 
    Revision: 8fa6ce7945b640e6168488e4417f9bb96e4ab46c 
    Groovy: 2.4.12 
    Ant: Apache Ant(TM) version 1.9.9 compiled on February 2 2017 
    JVM: 1.8.0_161 (Oracle Corporation 25.161-b12) 
    OS: Linux 4.4.0-92-generic amd64
  4. Jenkins

    Całkiem spoko, jak do tej pory. Teraz czas na gwiazdę odcinka!

    Dodajemy repozytorium do nasze list w Ubuntu i aktualizujemy jak w przypadku Java’y:

    echo "deb https://pkg.jenkins.io/debian binary/" >> /etc/apt/sources.list
    apt-get update

    Teraz pora na główne zaklęcie:

    apt-get install jenkins

    Notatka: Instalator zapyta nas czy zainstalować aplikację oraz o to czy ma instalować bez sprawdzania poprawności. Ja należę do tych, którzy wierzą w oficjalną wersję zamieszczoną na oficjalnych stronach. W sumie ktoś by zauważył, gdyby coś niezdrowego się działo.

    Po instalacji może sprawdzić czy nasz serwis działa. Wykonaj komendę:

    service jenkins status

    Teraz czas na konfigurację:

    Przejdźmy na stronę:

    jenkins.e-strix.com:8080
    

    Widzimy że aplikacja działa. Super!
    Teraz mamy do wpisania klucz, o który prosi nas pierwszy formularz. Pobierzmy go zgodnie z instrukcją:

    cat /var/lib/jenkins/secrets/initialAdminPassword

    Teraz wybierzmy sobie opcje, które pluginy mają być dodane przy instalacji. Ja wybieram wersję domyślną. Do naszej pracy wystarczy ta opcja, ale o tym będzie dalej.

    Teraz sobie chwilę poczekajmy aż instalacja przejdzie do końca i pojawi się formularz w którym będziemy poproszeni o utworzenie pierwszego administratora.

    W sumie działa, zatem możemy na tym zakończyć… Ale kole mnie po oczach ten port w pasku przeglądarki. Zatem zróbmy sobie ProxyPass. O tym w kolejnej części o serwerze Apache.

     

  5. Apache

    W zasadzie możemy pominąć kilka pierwszysch kroków instalacji ponieważ poruszałem ten temat już w dwóch wpisach, na temat Subversion/SVN oraz o Git’cie. Ale przypomnę w dość szybki sposób.

    A zatem instalujemy:

    apt-get install apache2

    Włączamy potrzebne moduły:

    a2enmod rewrite
    a2enmod proxy
    a2enmod proxy_http
    a2enmod headers
    a2enmod proxy_html
    a2enmod xml2enc

    i restartujemy serwer Apache:

    service apache2 restart

    Ok, teraz przejdźmy do katalogu dostępnych stron:

    cd /etc/apache2/sites-available/

    Tworzymy katalog naszej strony:

    mkdir -p /var/www/jenkins.e-strix.com/public_html/

    i dalej konfiguracja naszej strony:

    vim jenkins.e-strix.com.conf

    i jej zawartość

    <VirtualHost *:80>
    
     ServerAdmin k.mucik@e-strix.pl
     DocumentRoot /var/www/jenkins.e-strx.com/public_html
    
     <Directory "/var/www/jenkins.e-strx.com/public_html/" >
      Options Indexes FollowSymLinks MultiViews
      AllowOverride All
      allow from all
      Require all granted
    
      DirectoryIndex index.html
     </Directory>
    
     ServerName jenkins.e-strix.com
    
     ProxyRequests Off
     AllowEncodedSlashes NoDecode
    
     ProxyPass / http://127.0.0.1:8080/ nocanon
     ProxyPassReverse / http://127.0.0.1:8080/
     ProxyPassReverse / http://jenkins.e-strix.com/
    
     ErrorLog /var/www/jenkins.e-strix.com/error.log
     CustomLog /var/www/jenkins.e-strix.com/access.log combined
    
    </VirtualHost>

    I gra gitara… Teraz dodajemy naszą stronę i przeładowywujemy ostawienia:

    a2ensite jenkins.e-strix.com
    service apache2 reload

    Podsumowanie

    Teraz mamy już prawie wszystko aby móc automatycznie budować i publikować projekty.

    Prawie? Czyli co jeszcze?

Odpowiedź jest dość prosta, brakuje nam projektów oraz miejsca gdzie będzie publikacja. O tych elementach będą kolejne wpisy. Projekty oczywiście będą korzystały z naszej dokonanej pracy.

svn + WebSVN

Wstęp

Każdy etap prowadzenia projektu wiąże się nierozerwalnie z posiadaniem kodu oraz dokumentacji do projektu. Każdy programista wie jak ważny jest czas poświęcony na tworzenie algorytmów i ich implementacja. Jeśli jesteś osobą, która nie doświadczyła jeszcze utraty kodu poprzez błąd systemu lub dysku, spokojnie możesz zignorować poniższy tutorial, w innym przypadku zapraszam do zapoznania się z instrukcją jak postawić własny serwer kontroli wersji za złotówkę miesięcznie.

Oczywiście zapytasz czy słyszałem o dobrodziejstwie takim jak GitHub czy inny serwer. Tak, coś mi się obiło przypadkiem o uszy… Problem jest taki że darmowe konta są publiczne a prywatne kosztuje dziś 7 dolarów amerykańskich. Za tę kwotę można spokojnie wykupić sobie  serwer prywatny (trochę więcej niż wersję podstawową) i samodzielnie postawić odpowiednie narzędzia. Nie będę opisywał jak korzystać z Subversion, jest tego pełno w internecie…

W tym artykule poruszę konfigurację Subversion, opowiem o tym jak zainstalować go na serwerze z systemem Ubuntu, skonfigurować odpowiednie pliki oraz ścieżki. W dalszej części pokażę jak zainstalować klienta webowego WebSVN do przyjemnego podglądania zmian w repozytorium. Jeśli jesteś fanem gita, zachęcam do zobaczenia artykułu o instalacji gita wraz z klientem webowym GitList tutaj.

 

L.P. Czego potrzebujemy
1 VPS
2 System Operacyjny ja wybrałem Ubuntu 16.04
3 Dostęp po SSH z prawami root
4 http://websvn.tigris.org/files/documents/1380/49057/websvn-2.3.3.zip
5 Skonfigurowana domena na serwer VPS

Instalujemy PHP

Serwer który ja wybrałem oferuje instancję Ubuntu 16.04, która posiada w sobie zainstalowaną wersję PHP 7.0, jest ona wystarczalna dla naszych potrzeb. Jeśli zdecydujesz się na instalację wyższej wersji lub niższej, też jest ok. Wykorzystywane narzędzia wymagają PHP 5, zatem bez wahania decyduj.

apt-get install php7.0 php7.0-fpm -y

Instalujemy Apache

Kolejny krok to instalcja serwera apache, może również wykorzystać nginx. Ja korzystam z Apache chyba przez zasiedzenie. Jest on dość prymitywny ale działa i jest łatwy do konfigurowania. Apache spokojnie może obsługiwać do 250 użytkowników jednocześnie więc dla niewielkich stron można śmiało przy nim zostać, jeśli przewidujemy ruch do 1000 użytkowników jednocześnie zainteresujmy się serwerem nginx lub stworzeniem farmy serwerów.

Wykonajmy polecenie:

apt-get install subversion apache2 libapache2-svn libapache2-mod-php php-xml-parser

Ok, teraz mamy zainstalowany serwer i czas na konfigurację. Przejdźmy do katalogu dostępnych stron w serwerze i stwórzy nasz plik konfiguracyjny:

cd /etc/apache2/sites-available/
vim svn.e-strix.com.conf

Ja używam Vim’a do edycji, ale użyj dowolnie. Początki z edytorami textowymi są trudne ale warte nauki.

Plik wypełnij zawartością wg nw. listingu:

<VirtualHost *:80> 
ServerAdmin kontakt@e-strix.pl 
ServerName svn.e-strix.com 

DocumentRoot /var/www/svn.e-strix.com/public_html/ 

<Directory "/var/www/svn.e-strix.com/public_html/"> 
DirectoryIndex index.php index.html 
Options FollowSymLinks 
AllowOverride All 
</Directory> 

<Location /repo> 
DAV svn 
SVNParentPath /home/svnuser/repo 
SVNListParentPath On 
</Location> 

ErrorLog /var/www/svn.e-strix.com/error.log 
CustomLog /var/www/svn.e-strix.com/access.log combined 
</VirtualHost>

Stwórz katalog w którym będą nasze aplikacje:

mkdir -p /var/www/svn.e-strix.com/

ustaw stronę jako aktywną i przeładuj ustawienia Apache:

a2ensite svn.e-strix.com
service apache2 reload

Tworzymy repozytorium

Repozytorium subversion podobnie jak git’a może być gdziekolwiek na dysku. Dla zachowania przejrzystości, utworzymy konto użytkownika svn a w nim katalog z naszymi projektami.

Zatem dodajemy użytkownika i uzupełniamy dane o które poprosi system:

adduser svnuser
usermod -a -G svnuser www-data

Dalej tworzy nasz katalog repozytorium:

mkdir -p /home/svnuser/repo/

Gdy poprawnie zainstalujemy pakiet subversion, dostępne będzie dla nas narzędzie svnadmin do zarządzania projektami. Korzystamy z niego dodając nowe repozytorium:

svnadmin create /home/svnuser/repo/project1

Przejdźmy teraz do innego katalogu, ja wybrałem katalog /tmp i pobierzmy kopię repozytorium. W tym przypadku posłużę się kopiowaniem z pliku, komenda co jest skrótem od checkout. A zatem:

cd /tmp/
svn co file:///home/svnuser/repo/project1

Subversion pobrał na projekt, wejdźmy do środka i stwórzmy dowolny plik. Ja wybrałem .svnignore, może być dowolny:

cd project1/
touch .svnignore

Teraz dodajmy go używając komendy add oraz wypchnijmy zmiany do naszego repozytorium ci -m „comment”, bardzo ważne jest używanie przełącznika -m:

svn add .svnignore
svn ci -m "init commit"

Dodajemy klienta webowego

Jest wiele dostępnych na rynku klientów webowych, każdy z nich działa. Nie będę się rozdrabniał na to, który jest mikrosekundę szybszy czy ma lepiej napisany kod. To jest dla nas nie istotne bo i tak w większości będziemy pracować na lokalnych maszynach w wybranym IDE lub konsoli. WebSVN u mnie wygrał ze względu na intuicyjność i funkcjonalność. Ma też przyjemny interfejs, który mnie nie denerwuje więc dobrze mi się z nim pracuje.

W pierwszej kolejności musimy nadać prawa do katalogu repozytorium dla użytkownika www-data, który jest wykorzystywany przez serwer www.

Notatka: Nie jest to część poświęcona idealnemu skonfigurowaniu katalogów. Jeśli masz życzenie tworzyć hierarchię grup i użytkowników to śmiało.

Zatem zmieniamy właściciela katalogu:

cd /home/svnuser
chown -R svnuser:www-data repo/

Przejdźmy teraz do naszego katalogu aplikacji i pobierzmy klienta:

Notatka: wymagany jest program wget.

cd /var/www/svn.e-strix.com/
wget http://websvn.tigris.org/files/documents/1380/49057/websvn-2.3.3.zip

Teraz rozpakujmy pobrane archiwum, zmieńmy nazwę wypakowanego katalogu websvn-2.3.3 na public_html, zgodny z plikiem konfiguracyjnym serwera:

Notatka: Wymagany jest program unzip. Może nie być zainstalowany domyślnie.

unzip websvn-2.3.3.zip
mv websvn-2.3.3/ public_html/

Wyczyścimy śmieci i nadamy właściciela plików:

rm websvn-2.3.3.zip

chown -R www-data:www-data public_html/

Konfiguracja klienta

Teraz już z górki. Do pełnego sukcesu potrzebujemy jeszcze skonfigurować naszego klienta z utworzonym repozytorium.

Przechodzimy do katalogu public_html/include i w nim musimy utowrzyć plik konfiguracyjny. Całe szczęście że dostawca załączył przykładowy plik distconfig.php, który możemy wykorzystać… Utworzymy plik w tej lokalizacji o nazwie config.php.

cd public_html/include
vim config.php

Jego zawartość znajduje się na nw. listingu. Więc przekopiuj i zapisz plik:

<?php
$config->parentPath('/home/svnuser/repo');
$config->addTemplatePath($locwebsvnreal.'/templates/calm/','group');
?>

Teraz spokojnie możemy wpisać adres w przeglądarce i naszym oczom pojawi się zawartość naszego jednego repozytorium. Ale co w przypadku gdy bym miał ich więcej? Spokojnie… w przypadku gdy jest tylko jedno repo aplikacja od razu przekierowuje na jej zawartość, jeśli będzie więcej projektów, pierw ukaże się nam ich lista.

Dodajmy na serwerze kolejny projekt:

svnadmin create /home/svnuser/repo/project2
chown -R svnuser:www-data /home/svnuser/repo/project2/
chmod -R 774 /home/svnuser/repo/project2/

I teraz spróbujemy skorzystać z innego komputera, w taki sposób aby ruch odbył się przez sieć. Ja użyłem swojego laptopa.

Przechodzimy na dowolny katalog z prawami zapisu.

Teraz pobieramy nasze repozytorium:

svn co http://svn.e-strix.com/repo/project2/

Dodamy jakiś plik do projektu i wypchniemy zmiany na serwer:

cd project2/
vim index.html
svn add index.html
svn ci -m "init commit"

Zmiany możemy obserwować już na serwerze.

Podsumowanie

Systemy kontroli wersji są podstawą przy pracy programisty czy DevOps’a, szukanie błędów w kodzie czy winnych jest dużo łatwiejsze gdy można śledzić każdą zmianę. Powstaje także odpowiedzialność osoby za swoje „dzieło”. Kolejnym plusem okazuje się możliwość zdalnego budowania poprzez odpowiednie narzędzia CI takie jak Hudsom, Jenkins, Travis czy TeamCity, które można ustawić w taki sposób aby czasowo budowały aplikację czy dla każdego komitu. Tak rozproszony system bardzo przyspiesza pracę nad projektem i pozwala deweloperowi skupić się nad konkretnym zadaniem.

Co wybrać? Czy Subversion czy Git? Ja używam obu w pracy, dla nowych projektów zainteresowałbym się gitem ale i svn też jest bardzo popularny. Pamiętajcie że duże instytucje nie lubią zmian…