Monday, June 8, 2026

Java: http and https (self sign certificate)

install mkcert


# apt install mkcert

go to root of netbeans project and create self sign certificate


$ mkcert localhost
Created a new local CA 💥
Note: the local CA is not installed in the system trust store.
Note: the local CA is not installed in the Firefox and/or Chrome/Chromium trust store.
Run "mkcert -install" for certificates to be trusted automatically ⚠️

Created a new certificate valid for the following names 📜
 - "localhost"

The certificate is at "./localhost.pem" and the key at "./localhost-key.pem" ✅

It will expire on 8 September 2028 🗓

Convert to PKCS 12


$ openssl pkcs12 -export \
  -in localhost.pem \
  -inkey localhost-key.pem \
  -out localhost.p12 \
  -name localhost
Enter Export Password:
Verifying - Enter Export Password:

main java code


/*
 * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
 */

package com.dedetok.ddtescpos;

import com.alibaba.fastjson2.JSONObject;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;


/**
 *
 * @author dedetok
 */
public class DdtEscPos {

    final static String serviceString = "DdtEscPos";
    final static String serviceVersion = "1.0";
    
    public static void main(String[] args) {
        System.out.println(serviceString+" "+serviceVersion);
        
        // ================= 
        // safe port  49152 to 65535
        // build http
        int httpPort = 50001;
        // build https
        int httpsPort = 50002;
        try {
            HttpServer httpServer = createHttpService(httpPort);
            HttpsServer httpsServer = createHttpsService(httpsPort);
            
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                System.out.println("Stopping HTTP & HTTPS server...");
                httpServer.stop(5); // stop 5 second
                System.out.println("HTTP server stopped");
                httpsServer.stop(5); // stop 5 second
                System.out.println("HTTPS server stopped");

            }));
            httpServer.start();
            System.out.println("HTTP server running on http://localhost:" + httpPort);
            httpsServer.start();
            System.out.println("HTTPS server running on http://localhost:" + httpsPort);

        } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException ex) {
            System.getLogger(DdtEscPos.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
        }
    }

    /*
     * create http service in httpPort
     */
    static HttpsServer createHttpsService(int httpsPort) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException {
        // Load PKCS12 keystore
        char[] password = "escpos".toCharArray();
        KeyStore ks = KeyStore.getInstance("PKCS12");
        try (FileInputStream fis = new FileInputStream("localhost.p12")) {
            ks.load(fis, password);
        }

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(
                KeyManagerFactory.getDefaultAlgorithm());
        
        kmf.init(ks, password);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), null, null);

        // Create HTTPS server
        HttpsServer httpsServer =
                HttpsServer.create(
                        new InetSocketAddress("localhost", httpsPort),
                        0);        
    
        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
        
        // Register endpoints
        httpsServer.createContext("/", DdtEscPos::handleHttpRequest);
        httpsServer.createContext("/printjson", DdtEscPos::handlePrintJson);        
        
        return httpsServer;
    }
    
    /*
     * create http service in httpPort
     */
    static HttpServer createHttpService(int httpPort) throws IOException {
        HttpServer httpServer =
                HttpServer.create(new InetSocketAddress("localhost", httpPort), 0);
        
        httpServer.createContext("/", DdtEscPos::handleHttpRequest);
        httpServer.createContext("/printjson", DdtEscPos::handlePrintJson);
        return httpServer;
    }

    /*
     * handling /
     */
    static void handleHttpRequest(HttpExchange exchange) throws IOException {
        // CORS
        exchange.getResponseHeaders().add(
                "Access-Control-Allow-Origin", "*");

        exchange.getResponseHeaders().add(
                "Access-Control-Allow-Headers",
                "Content-Type, Authorization");

        exchange.getResponseHeaders().add(
        "Access-Control-Allow-Methods",
        "GET, POST, OPTIONS");

        if ("OPTIONS".equalsIgnoreCase(exchange.getRequestMethod())) {
            exchange.sendResponseHeaders(204, -1);
            return;
        }
        
        // UTF-8 text response
        exchange.getResponseHeaders().add(
                "Content-Type",
                "text/plain; charset=UTF-8");
        
        String response = infoService(exchange).toString();

        byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8);

        exchange.sendResponseHeaders(200, responseBytes.length);

        
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(responseBytes);
        }
        System.out.println("Serviing client done"); // debug
    }

    /*
     * handling /printjson
     */
    static void handlePrintJson(HttpExchange exchange) throws IOException {
        // CORS
        exchange.getResponseHeaders().add(
                "Access-Control-Allow-Origin", "*");

        exchange.getResponseHeaders().add(
                "Access-Control-Allow-Headers",
                "Content-Type, Authorization");

        exchange.getResponseHeaders().add(
        "Access-Control-Allow-Methods",
        "GET, POST, OPTIONS");

        if ("OPTIONS".equalsIgnoreCase(exchange.getRequestMethod())) {
            exchange.sendResponseHeaders(204, -1);
            return;
        }
        
        // UTF-8 text response
        exchange.getResponseHeaders().add(
                "Content-Type",
                "text/plain; charset=UTF-8");
        // get json from request
        // Read request body
        String requestBody;
        try (InputStream is = exchange.getRequestBody()) {
            requestBody = new String(is.readAllBytes(), StandardCharsets.UTF_8);
            // Parse JSON
            // Parse JSON and process
            JSONObject json = JSONObject.parseObject(requestBody);
            MyController.printJson(json.toString());
            
            JSONObject responseJson = infoService(exchange);
            responseJson.put("status", "printing");
            
            byte[] bytes =
                    responseJson.toString().getBytes(StandardCharsets.UTF_8);

            exchange.sendResponseHeaders(200, bytes.length);
            OutputStream os = exchange.getResponseBody();
            os.write(bytes);
            os.flush();

        }
    }
    
    /* 
     * standard info response 
     */
    static JSONObject infoService(HttpExchange exchange) {
        JSONObject jsonObj = new JSONObject();
        
        jsonObj.put("version", serviceVersion);
        jsonObj.put("service", serviceString);
        jsonObj.put("status", "running");
        String host = exchange.getLocalAddress().getHostString();
        int port = exchange.getLocalAddress().getPort();

        String baseUrl = "http://" + host + ":" + port;
        jsonObj.put("endpoint",baseUrl+"/printjson");
        
        return jsonObj;
    }
}

to run service


$ java -jar target/DdtEscPos-1.0.jar 
Hello World!
HTTP server running on http://localhost:50001
HTTPS server running on https://localhost:50002
Serviing client done
Serviing client done

using curl to send json to print http


$ curl -H "Content-Type: application/json" \
     -d @exampleescpos.json \
     http://localhost:50001/printjson

https


$ curl -k \
  -H "Content-Type: application/json" \
  -d @exampleescpos.json \
  https://localhost:50002/printjson

or 


$ curl --insecure \
  -H "Content-Type: application/json" \
  -d @exampleescpos.json \
  https://localhost:50002/printjson

Credit: esc html using https://www.freeformatter.com/html-escape.html 

Saturday, June 6, 2026

Java: using thermal printer 6106 - 80mm esc/pos java in debian 13 bluetooth

Install bluetooth software


$ apt install firmware-misc-nonfree bluez bluez-tools blueman screen

linux user groups: bluetooth, lp, netdev, sudo, and plugdev

add linux user to group


# usermod -aG sudo username

check your bluetooth adapter has been recognize by debian


$ lsusb
...
Bus 001 Device 003: ID 0489:e0cd Foxconn / Hon Hai MediaTek Bluetooth Adapter
...

pair your device, if you don't hook it in boot, you need to rebind after boot. 


$ bluetoothctl pair 86:67:7A:1D:C2:E3
Attempting to pair with 86:67:7A:1D:C2:E3
[CHG] Device 86:67:7A:1D:C2:E3 Connected: yes
Failed to pair: org.bluez.Error.AuthenticationFailed

using bluetoothctl to handle bluetooth password


$ bluetoothctl
Agent registered
[bluetoothctl]> agent KeyboardOnly
Agent is already registered
[bluetoothctl]> default-agent
Default agent request successful
[bluetoothctl]> scan on
SetDiscoveryFilter success
Discovery started
[CHG] Controller BC:F4:D4:12:4C:28 Discovering: yes
[NEW] Device 4D:F9:CE:48:C4:1B 4D-F9-CE-48-C4-1B
[NEW] Device 62:FE:E1:08:CD:73 62-FE-E1-08-CD-73
[NEW] Device 6D:AC:09:7B:A7:F3 6D-AC-09-7B-A7-F3
[NEW] Device 86:67:7A:1D:C2:E3 RPP02N
[bluetoothctl]> pair 86:67:7A:1D:C2:E3
Attempting to pair with 86:67:7A:1D:C2:E3
[CHG] Device 86:67:7A:1D:C2:E3 Connected: yes
[CHG] Device 86:67:7A:1D:C2:E3 Bonded: yes
[CHG] Device 86:67:7A:1D:C2:E3 Modalias: usb:v05ACp0239d0644
[CHG] Device 86:67:7A:1D:C2:E3 ServicesResolved: yes
[CHG] Device 86:67:7A:1D:C2:E3 Paired: yes
Pairing successful
[DEL] Device 6D:AC:09:7B:A7:F3 6D-AC-09-7B-A7-F3
[CHG] Device 86:67:7A:1D:C2:E3 ServicesResolved: no
[CHG] Device 86:67:7A:1D:C2:E3 Connected: no
[DEL] Device 4D:F9:CE:48:C4:1B 4D-F9-CE-48-C4-1B
[DEL] Device 62:FE:E1:08:CD:73 62-FE-E1-08-CD-73
[bluetoothctl]> trust 86:67:7A:1D:C2:E3
[CHG] Device 86:67:7A:1D:C2:E3 Trusted: yes
Changing 86:67:7A:1D:C2:E3 trust succeeded

busctl output


$ busctl introspect org.bluez /org/bluez/hci0/dev_86_67_7A_1D_C2_E3
NAME                                TYPE      SIGNATURE RESULT/VALUE                             FLAGS
org.bluez.Device1                   interface -         -                                        -
.CancelPairing                      method    -         -                                        -
.Connect                            method    -         -                                        -
.ConnectProfile                     method    s         -                                        -
.Disconnect                         method    -         -                                        -
.DisconnectProfile                  method    s         -                                        -
.Pair                               method    -         -                                        -
.Adapter                            property  o         "/org/bluez/hci0"                        emits-change
.Address                            property  s         "86:67:7A:1D:C2:E3"                      emits-change
.AddressType                        property  s         "public"                                 emits-change
.AdvertisingData                    property  a{yv}     -                                        emits-change
.AdvertisingFlags                   property  ay        -                                        emits-change
.Alias                              property  s         "RPP02N"                                 emits-change writable
.Appearance                         property  q         -                                        emits-change
.Blocked                            property  b         false                                    emits-change writable
.Bonded                             property  b         true                                     emits-change
.Class                              property  u         263808                                   emits-change
.Connected                          property  b         false                                    emits-change
.Icon                               property  s         "printer"                                emits-change
.LegacyPairing                      property  b         false                                    emits-change
.ManufacturerData                   property  a{qv}     -                                        emits-change
.Modalias                           property  s         "usb:v05ACp0239d0644"                    emits-change
.Name                               property  s         "RPP02N"                                 emits-change
.Paired                             property  b         true                                     emits-change
.RSSI                               property  n         -                                        emits-change
.ServiceData                        property  a{sv}     -                                        emits-change
.ServicesResolved                   property  b         false                                    emits-change
.Sets                               property  a{oa{sv}} -                                        emits-change
.Trusted                            property  b         true                                     emits-change writable
.TxPower                            property  n         -                                        emits-change
.UUIDs                              property  as        7 "00000001-0000-1000-8000-00805f9b34fb… emits-change
.WakeAllowed                        property  b         -                                        emits-change writable
org.freedesktop.DBus.Introspectable interface -         -                                        -
.Introspect                         method    -         s                                        -
org.freedesktop.DBus.Properties     interface -         -                                        -
.Get                                method    ss        v                                        -
.GetAll                             method    s         a{sv}                                    -
.Set                                method    ssv       -                                        -
.PropertiesChanged                  signal    sa{sv}as  -                                        -

bind to rfcomm (sudo), if you don't hook it in boot, you need to rebind after boot.


  # rfcomm bind rfcomm0 86:67:7A:1D:C2:E3

or with sudo


  $ sudo  rfcomm bind rfcomm0 86:67:7A:1D:C2:E3

your dev access point


$ ls /dev/ | grep rfcomm
rfcomm0

get your bluetooth device info


$ bluetoothctl info 86:67:7A:1D:C2:E3
Device 86:67:7A:1D:C2:E3 (public)
	Name: RPP02N
	Alias: RPP02N
	Class: 0x00040680 (263808)
	Icon: printer
	Paired: yes
	Bonded: yes
	Trusted: yes
	Blocked: no
	Connected: no
	LegacyPairing: no
	UUID: SDP                       (00000001-0000-1000-8000-00805f9b34fb)
	UUID: RFCOMM                    (00000003-0000-1000-8000-00805f9b34fb)
	UUID: L2CAP                     (00000100-0000-1000-8000-00805f9b34fb)
	UUID: Serial Port               (00001101-0000-1000-8000-00805f9b34fb)
	UUID: PnP Information           (00001200-0000-1000-8000-00805f9b34fb)
	UUID: Unknown                   (000018f0-0000-1000-8000-00805f9b34fb)
	UUID: Vendor specific           (e7810a71-73ae-499d-8c15-faa9aef0c3f2)
	Modalias: usb:v05ACp0239d0644

Optional: to let systemd handling pair device

create/edit bluetooth-printer.service (or any name you wish), at /etc/systemd/system/, e.g   /etc/systemd/system/bluetooth-printer.service.


[Unit]
Description=Bind Bluetooth Thermal Printer to rfcomm0
After=bluetooth.service
Requires=bluetooth.service

[Service]
Type=oneshot
ExecStart=/usr/bin/rfcomm bind rfcomm0 86:67:7A:1D:C2:E3
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

After that use systemd to handle pairing

  • sudo systemctl daemon-reload
  • sudo systemctl enable bluetooth-printer.service
  • sudo systemctl start bluetooth-printer.service
  • systemctl status bluetooth-printer.service 

you can try previous code https://dedetoknotes.blogspot.com/2026/06/java-using-thermal-printer-6106-80mm.html by change 

devicePath = "/dev/usb/lp1" 

to

devicePath = "/dev/rfcomm0" 

Credit: https://www.freeformatter.com/html-escape.html#before-output to esc html 

Monday, June 1, 2026

Java: using thermal printer 6106 - 80mm esc/pos java in debian 13 usb

insert your printer using usb cable


# lsusb
...
Bus 008 Device 002: ID 28e9:0289 GDMicroelectronics micro-printer
Bus 009 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
# dmesg
[   70.532011] usb 8-1: new full-speed USB device number 2 using xhci_hcd
[   70.688254] usb 8-1: New USB device found, idVendor=28e9, idProduct=0289, bcdDevice= 2.00
[   70.688270] usb 8-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[   70.688276] usb 8-1: Product: micro-printer
[   70.688282] usb 8-1: Manufacturer: GEZHI
[   70.688286] usb 8-1: SerialNumber: 000000000004
[   70.730721] usblp 8-1:1.0: usblp1: USB Bidirectional printer dev 2 if 0 alt 0 proto 2 vid 0x28E9 pid 0x0289
[   70.730783] usbcore: registered new interface driver usblp
use nl80211
# ls -al /dev/usb/
total 0
drwxr-xr-x  2 root root     80 May 31 14:31 .
drwxr-xr-x 20 root root   3840 May 31 14:31 ..
crw-------  1 root root 180, 0 May 31 14:30 hiddev0
crw-rw----  1 root lp   180, 1 May 31 14:31 lp1
# ls -al /dev/usb/lp1
crw-rw---- 1 root lp 180, 1 May 31 14:31 /dev/usb/lp1

we found the device known as usb lp1 i.e. /dev/usb/lp1. test to print something.


# printf '\x1b\x40Hello World\n\n\n' > /dev/usb/lp1

your printer should print "Hello World"

add your account or linux user as lp group 


# usermod -aG lp [username]

you need to sign out and re sign in, then try to print as [username]


$ printf '\x1b\x40Hello World\n\n\n' > /dev/usb/lp1

if everything work, now you can work as [username]. this is simple netbeans 30 - maven, to test your printer.

This is pom.xml for your project.


<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.dedetok</groupId>
    <artifactId>EnumComPort</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.release>21</maven.compiler.release>
        <exec.mainClass>com.dedetok.enumcomport.EnumComPort</exec.mainClass>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.fazecast</groupId>
            <artifactId>jSerialComm</artifactId>
            <version>2.11.0</version>
            <type>jar</type>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.0</version>

                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>

                        <configuration>
                            <createDependencyReducedPom>false</createDependencyReducedPom>

                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.dedetok.enumcomport.EnumComPort</mainClass>
                                </transformer>
                            </transformers>

                        </configuration>
                    </execution>
                </executions>

            </plugin>

        </plugins>
    </build>
</project>

This is the java code


package com.dedetok.enumcomport;

import com.fazecast.jSerialComm.SerialPort;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 *
 * @author dedetok
 */
public class EnumComPort {

    public static void main(String[] args) throws IOException {
        System.out.println("Hello World!");
        System.out.println(System.getProperty("user.name"));
        Process p = Runtime.getRuntime().exec("id");
        p.getInputStream().transferTo(System.out);
        
        SerialPort[] ports = SerialPort.getCommPorts();

        if (ports.length == 0) {
            System.out.println("No serial ports detected.");
        }

        System.out.println("Available Serial Ports:");
        System.out.println("------------------------");

        for (SerialPort port : ports) {

            System.out.println("System Port Name : " + port.getSystemPortName());
            System.out.println("Descriptive Name: " + port.getDescriptivePortName());
            System.out.println("Port Description: " + port.getPortDescription());
            System.out.println("System Location : " + port.getSystemPortPath());
            System.out.println("------------------------");
        }
        
        String devicePath = "/dev/usb/lp1";
        // Open file in read-write mode for raw bidirectional access
        RandomAccessFile deviceFile = new RandomAccessFile(devicePath, "rw");
        FileDescriptor fd = deviceFile.getFD();
        
        try (OutputStream out = new FileOutputStream(fd)) {
            // ESC @ (initialize)
            out.write(new byte[]{0x1B, 0x40});

            out.write("Hello World\n".getBytes());

            // Feed
            out.write("\n\n".getBytes());

            // Cut
            out.write(new byte[]{0x1D, 0x56, 0x00});

            out.flush();
            out.close(); // NO NEED
        } catch (FileNotFoundException ex) {
            System.getLogger(EnumComPort.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
        }
        System.out.println("End");
    }
}

In my test, you can run inside your netbeans, Run -> Build Project or Run -> Clean and Run Project. to run the jar, you go to project root and run like this


$ java -jar ./target/EnumComPort-1.0.jar 
Hello World!
[username]
uid=1000([username]) gid=1000([username]) groups=1000([username]),7(lp),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),33(www-data),44(video),46(plugdev),100(users),106(netdev),111(bluetooth),113(lpadmin),116(scanner),124(libvirt)
No serial ports detected.
Available Serial Ports:
------------------------
End

your print should print "Hello World". "No serial ports" means, you can use com.fazecast.jSerialComm library for lp printer.

Credit: https://www.freeformatter.com/html-escape.html for free html escape 

Saturday, May 30, 2026

Java: enumerate com serial using fazecast jSerialComm

Simple code to enumerate serial com port


/*
 * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
 */

package com.dedetok.enumcomport;

import com.fazecast.jSerialComm.SerialPort;

/**
 *
 * @author dedetok
 */
public class EnumComPort {

    public static void main(String[] args) {
        System.out.println("Hello World!");

        SerialPort[] ports = SerialPort.getCommPorts();

        if (ports.length == 0) {
            System.out.println("No serial ports detected.");
            return;
        }

        System.out.println("Available Serial Ports:");
        System.out.println("------------------------");

        for (SerialPort port : ports) {

            System.out.println("System Port Name : " + port.getSystemPortName());
            System.out.println("Descriptive Name: " + port.getDescriptivePortName());
            System.out.println("Port Description: " + port.getPortDescription());
            System.out.println("System Location : " + port.getSystemPortPath());
            System.out.println("------------------------");
        }
        
    }
}

Note: lpx device (e.g /dev/usb/lp1) does not use serial com, you need to send hex code direct via OutputStream.

Java: Hook shutdown to run java application as systemd

This is minimum java code to hook shutdown


/*
 * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
 */

package com.dedetok.javaservicetest;

/**
 *
 * @author dedetok
 */
public class JavaServiceTest {

    private static volatile boolean running = true;
        
    public static void main(String[] args) {
        System.out.println("Hello World!");
        
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Shutdown hook triggered");

            running = false;

            // Cleanup resources here
            // Close database connections
            // Flush logs
            // Stop worker threads
            // Main shutdown path
            //cleanupResources();

            System.out.println("Cleanup complete");
        }));

        System.out.println("Daemon started");

        while (running) {
            System.out.println("Working...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException ex) {
                System.getLogger(JavaServiceTest.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
            }
        }

    }
}

to run/test this application, you can not run it inside netbeans. use terminal to run it, go to your project root and run


$ java -cp target/classes \
     com.dedetok.javaservicetest.JavaServiceTest
Hello World!
Daemon started
Working...
Working...
^CShutdown hook triggered
Cleanup complete