4 Commits

17 changed files with 111 additions and 190 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
# Asset Direction Target TRIGGER SEVERITY NOTE # Asset Direction Target TRIGGER SEVERITY NOTE
############################################################# #############################################################
SOL BELOW 70.0 ONETIME 2 "ALARM: Risiko for Perps Solana long LIKVIDERING!" SOL BELOW 170.0 ONETIME INFO "EMERGENCY: Risiko for Perps Solana long LIKVIDERING!"
SOL BELOW 71.4 ONETIME 2 "ALARM: Risiko for Solana Raydium LÅN LIKVIDERING!" #SOL BELOW 71.4 ONETIME CRITICAL "CRITICAL: Risiko for Solana Raydium LÅN LIKVIDERING!"
-59
View File
@@ -1,59 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
JREVERSION=25.0.2-0
export VERSION=$(git describe --tags --exact-match 2>/dev/null \
|| git symbolic-ref --short -q HEAD \
|| git rev-parse --short HEAD)
GITHASH=$(git rev-parse --short=8 HEAD)
echo "Preparing Docker image for '${VERSION}'..."
mkdir -p build/docker/{conf,libs}
cp src/main/docker/run.sh build/docker/run.sh
cp libs/*.jar build/docker/libs/
cp conf/* build/docker/conf/
sed \
-e "s|_VERSION_|${VERSION}|g" \
-e "s|_JREVERSION_|${JREVERSION}|g" \
src/main/docker/Dockerfile_template \
> build/docker/Dockerfile
GROUP=r35157
NAME=nenjimhub
BASETAG=dockerreg.r35157.com/${GROUP}/${NAME}
HASHTAG=${BASETAG}:${GITHASH}_amd64
VERSIONTAG=${BASETAG}:${VERSION}_amd64
LATESTTAG=${BASETAG}:latest_amd64
echo "Building ${HASHTAG}..."
docker buildx build \
--load \
-t ${HASHTAG} \
build/docker
docker tag ${HASHTAG} ${VERSIONTAG}
docker tag ${HASHTAG} ${LATESTTAG}
sed \
-e "s|_HASHTAG_|${HASHTAG}|g" \
-e "s|_VERSIONTAG_|${VERSIONTAG}|g" \
-e "s|_LATESTTAG_|${LATESTTAG}|g" \
src/main/docker/publish_template.sh \
> build/docker/publish.sh
chmod 755 build/docker/publish.sh
echo ""
echo "Docker image ready:"
echo " ${HASHTAG}"
echo " ${VERSIONTAG}"
echo " ${LATESTTAG}"
-5
View File
@@ -5,10 +5,5 @@ export VERSION=$(git describe --tags --exact-match 2>/dev/null \
|| git symbolic-ref --short -q HEAD \ || git symbolic-ref --short -q HEAD \
|| git rev-parse --short HEAD) || git rev-parse --short HEAD)
./docker.sh
echo "Publishing artifact to local Maven repo ($VERSION)..." echo "Publishing artifact to local Maven repo ($VERSION)..."
./gradlew -Pversion=$VERSION publishToMavenLocal ./gradlew -Pversion=$VERSION publishToMavenLocal
echo "Publishing docker container to public 'dockerreg.r35157.com' ($VERSION)..."
./build/docker/publish.sh
-1
View File
@@ -1 +0,0 @@
src/main/docker/run.sh
Executable
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
jars=(libs/*.jar)
if (( ${#jars[@]} == 0 )); then
echo "ERROR: No JARs found in libs/" >&2
exit 1
fi
CLASSPATH=$(IFS=:; echo "${jars[*]}")
exec java \
--enable-preview \
-Dlog4j.configurationFile=conf/log4j2.xml \
-cp "$CLASSPATH" \
com.r35157.nenjim.hubd.impl.ref.Main
-43
View File
@@ -1,43 +0,0 @@
FROM dockerreg.r35157.com/r35157/jre:_JREVERSION__amd64
LABEL maintainer="Minimons <minimons@r35157.com>"
# Setup environment
ENV APP_HOME=/usr/local/software/nenjimhub
WORKDIR /usr/local/software
USER root
RUN mkdir nenjimhub-_VERSION_ \
&& ln -s nenjimhub-_VERSION_ nenjimhub
WORKDIR nenjimhub
RUN mkdir libs
# These dirs are expected to be overshadowed by host mounts
RUN mkdir conf logs && chown user:user logs
# Set timezone
ENV TZ=Europe/Copenhagen
RUN apt-get update
RUN apt-get install -y tzdata \
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone \
&& dpkg-reconfigure -f noninteractive tzdata
# Clean-up
RUN apt-get autoremove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install application and configuration (Do this as late as possible to be able to reuse layers between builds)
COPY run.sh .
COPY conf/* conf/
COPY libs/*.jar libs/
RUN chown root:root -R conf
RUN chmod 755 ./run.sh \
&& chmod -R a+rX /usr/local/software/nenjimhub-_VERSION_
USER user:user
CMD ["./run.sh"]
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
echo "Publishing '_HASHTAG_', '_VERSIONTAG_' and '_LATESTTAG_'..."
docker push _HASHTAG_
docker push _VERSIONTAG_
docker push _LATESTTAG_
echo "Publishing completed!"
-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
jars=(libs/*.jar)
if (( ${#jars[@]} == 0 )); then
echo "ERROR: No JARs found in libs/" >&2
exit 1
fi
CLASSPATH=$(IFS=:; echo "${jars[*]}")
exec java \
--enable-preview \
-Dlog4j.configurationFile=conf/log4j2.xml \
-cp "$CLASSPATH" \
com.r35157.nenjim.hubd.impl.ref.Main
-19
View File
@@ -1,19 +0,0 @@
version: '3.9'
services:
nenjimhub:
hostname: nenjimhub
image: dockerreg.r35157.com/r35157/nenjimhub:latest
deploy:
replicas: 1
resources:
limits:
memory: 128m
restart_policy:
condition: any
delay: 5s
max_attempts: 3
window: 120s
volumes:
- /home/op/nenjimhub/conf:/usr/local/software/nenjimhub/conf
- /home/op/nenjimhub/logs:/usr/local/software/nenjimhub/logs
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
docker stack deploy --compose-file /home/op/stack.yml stack --detach=true
@@ -0,0 +1,10 @@
package com.r35157.jupiterperpsalarm;
public enum AlarmSeverity {
EMERGENCY, // Repeated wake-up alarm - sound always
CRITICAL, // One-shot wake-up alarm - sound always
WARN, // Audible warning - respecting quiet hours though
INFO, // Normal notification with visual and audible feedback
SILENT, // Low-priority notification - visual feedback without sound/vibration
GHOST // No visual/audible notification - only visible inside the Pushover app
}
@@ -1,5 +1,7 @@
package com.r35157.jupiterperpsalarm.impl.ref; package com.r35157.jupiterperpsalarm.impl.ref;
import com.r35157.jupiterperpsalarm.AlarmSeverity;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.file.Files; import java.nio.file.Files;
@@ -44,15 +46,21 @@ public final class AlarmConfigurationParser {
JupiterPerpsAsset asset = JupiterPerpsAsset.valueOf( JupiterPerpsAsset asset = JupiterPerpsAsset.valueOf(
cursor.nextToken("asset").toUpperCase(Locale.ROOT) cursor.nextToken("asset").toUpperCase(Locale.ROOT)
); );
PriceDirection direction = PriceDirection.valueOf( PriceDirection direction = PriceDirection.valueOf(
cursor.nextToken("direction").toUpperCase(Locale.ROOT) cursor.nextToken("direction").toUpperCase(Locale.ROOT)
); );
BigDecimal target = new BigDecimal(cursor.nextToken("target")); BigDecimal target = new BigDecimal(cursor.nextToken("target"));
AlarmTrigger trigger = AlarmTrigger.valueOf( AlarmTrigger trigger = AlarmTrigger.valueOf(
cursor.nextToken("trigger").toUpperCase(Locale.ROOT) cursor.nextToken("trigger").toUpperCase(Locale.ROOT)
); );
int severity = Integer.parseInt(cursor.nextToken("severity"));
AlarmSeverity severity = AlarmSeverity.valueOf(
cursor.nextToken("severity").toUpperCase()
);
String note = cursor.nextQuotedString("note"); String note = cursor.nextQuotedString("note");
cursor.skipWhitespace(); cursor.skipWhitespace();
@@ -16,10 +16,9 @@ public final class JupiterPerpsAlarmImpl {
try { try {
config = Config.parse(args, System.getenv()); config = Config.parse(args, System.getenv());
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
System.err.println(exception.getMessage());
printUsage(); printUsage();
System.exit(2); String errMsg = "Could not parse configuration for JupiterPerpsAlarm: " + exception.getMessage() + "!";
return; throw new IllegalStateException(errMsg, exception);
} }
if (config.selfTest()) { if (config.selfTest()) {
@@ -32,9 +31,8 @@ public final class JupiterPerpsAlarmImpl {
try { try {
definitions = AlarmConfigurationParser.parse(config.alarmConfiguration()); definitions = AlarmConfigurationParser.parse(config.alarmConfiguration());
} catch (Exception exception) { } catch (Exception exception) {
System.err.println("Could not load alarm configuration: " + exception.getMessage()); String errMsg = "Could not load alarm configuration: " + exception.getMessage() + "!";
System.exit(2); throw new IllegalStateException(errMsg, exception);
return;
} }
List<AlarmAction> actions = new ArrayList<>(); List<AlarmAction> actions = new ArrayList<>();
@@ -91,7 +89,7 @@ public final class JupiterPerpsAlarmImpl {
asset.oracleAccount() asset.oracleAccount()
); );
assetDefinitions.forEach(definition -> System.out.printf( assetDefinitions.forEach(definition -> System.out.printf(
" %s %s USD, %s, severity=%d%n", " %s %s USD, %s, severity=%s%n",
definition.direction(), definition.direction(),
definition.target().toPlainString(), definition.target().toPlainString(),
definition.trigger(), definition.trigger(),
@@ -1,5 +1,7 @@
package com.r35157.jupiterperpsalarm.impl.ref; package com.r35157.jupiterperpsalarm.impl.ref;
import com.r35157.jupiterperpsalarm.AlarmSeverity;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Objects; import java.util.Objects;
@@ -8,7 +10,7 @@ public record PriceAlarmDefinition(
PriceDirection direction, PriceDirection direction,
BigDecimal target, BigDecimal target,
AlarmTrigger trigger, AlarmTrigger trigger,
int severity, AlarmSeverity severity,
String note String note
) { ) {
public PriceAlarmDefinition { public PriceAlarmDefinition {
@@ -18,11 +20,8 @@ public record PriceAlarmDefinition(
Objects.requireNonNull(trigger, "trigger"); Objects.requireNonNull(trigger, "trigger");
Objects.requireNonNull(note, "note"); Objects.requireNonNull(note, "note");
if (target.signum() <= 0) { if (target.signum() < 0) {
throw new IllegalArgumentException("Target price must be positive"); throw new IllegalArgumentException("Target price cannot be negative (was: " + target.signum() + ")!");
}
if (severity < 0) {
throw new IllegalArgumentException("Severity must be zero or positive");
} }
} }
} }
@@ -1,5 +1,7 @@
package com.r35157.jupiterperpsalarm.impl.ref; package com.r35157.jupiterperpsalarm.impl.ref;
import com.r35157.jupiterperpsalarm.AlarmSeverity;
import java.net.URI; import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.net.http.HttpClient; import java.net.http.HttpClient;
@@ -28,13 +30,13 @@ public final class PushoverAlarmAction implements AlarmAction {
price.slot() price.slot()
); );
// Severity and note are intentionally parsed and retained in the model, // TODO: Note is intentionally parsed and retained in the model, but are not used by Pushover yet.
// but are not used by Pushover yet. // https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref/issues/7
String body = form("token", applicationToken) + "&" + String body = form("token", applicationToken) + "&" +
form("user", userKey) + "&" + form("user", userKey) + "&" +
form("title", title) + "&" + form("title", title) + "&" +
form("message", message) + "&" + form("message", message) + "&" +
"priority=2&retry=30&expire=10800&sound=persistent"; createPushoverSeverityParameters(alarm.severity());;
HttpRequest request = HttpRequest.newBuilder(PUSHOVER_URI) HttpRequest request = HttpRequest.newBuilder(PUSHOVER_URI)
.timeout(Duration.ofSeconds(15)) .timeout(Duration.ofSeconds(15))
@@ -56,7 +58,7 @@ public final class PushoverAlarmAction implements AlarmAction {
response.body() response.body()
); );
} else { } else {
System.out.println("Pushover emergency alarm sent."); System.out.println("Pushover alarm sent: " + alarm.severity());
} }
}); });
} }
@@ -66,6 +68,17 @@ public final class PushoverAlarmAction implements AlarmAction {
URLEncoder.encode(value, StandardCharsets.UTF_8); URLEncoder.encode(value, StandardCharsets.UTF_8);
} }
private static String createPushoverSeverityParameters(AlarmSeverity severity) {
return switch (severity) {
case EMERGENCY -> "priority=2&retry=30&expire=10800&sound=persistent";
case CRITICAL -> "priority=1&sound=spacealarm";
case WARN -> "priority=0&sound=siren";
case INFO -> "priority=0";
case SILENT -> "priority=-1";
case GHOST -> "priority=-2";
};
}
private final HttpClient httpClient = HttpClient.newHttpClient(); private final HttpClient httpClient = HttpClient.newHttpClient();
private final String applicationToken; private final String applicationToken;
private final String userKey; private final String userKey;
@@ -1,5 +1,7 @@
package com.r35157.jupiterperpsalarm.impl.ref; package com.r35157.jupiterperpsalarm.impl.ref;
import com.r35157.jupiterperpsalarm.AlarmSeverity;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
@@ -17,14 +19,14 @@ public final class SelfTest {
private static void configurationParserTest() { private static void configurationParserTest() {
PriceAlarmDefinition alarm = AlarmConfigurationParser.parseLine( PriceAlarmDefinition alarm = AlarmConfigurationParser.parseLine(
"SOL ABOVE 75.7 ONETIME 2 \"ALARM: Risiko for likvidering!\"" "SOL ABOVE 75.7 ONETIME CRITICAL \"ALARM: Risiko for likvidering!\""
); );
require(alarm.asset() == JupiterPerpsAsset.SOL, "Asset parsing failed"); require(alarm.asset() == JupiterPerpsAsset.SOL, "Asset parsing failed");
require(alarm.direction() == PriceDirection.ABOVE, "Direction parsing failed"); require(alarm.direction() == PriceDirection.ABOVE, "Direction parsing failed");
require(alarm.target().compareTo(new BigDecimal("75.7")) == 0, "Target parsing failed"); require(alarm.target().compareTo(new BigDecimal("75.7")) == 0, "Target parsing failed");
require(alarm.trigger() == AlarmTrigger.ONETIME, "Trigger parsing failed"); require(alarm.trigger() == AlarmTrigger.ONETIME, "Trigger parsing failed");
require(alarm.severity() == 2, "Severity parsing failed"); require(alarm.severity() == AlarmSeverity.CRITICAL, "Severity parsing failed");
require(alarm.note().equals("ALARM: Risiko for likvidering!"), "Note parsing failed"); require(alarm.note().equals("ALARM: Risiko for likvidering!"), "Note parsing failed");
} }
@@ -89,7 +91,7 @@ public final class SelfTest {
direction, direction,
new BigDecimal(target), new BigDecimal(target),
trigger, trigger,
2, AlarmSeverity.CRITICAL,
"ignored for now" "ignored for now"
); );
} }
@@ -14,10 +14,13 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
public class Main { public class Main {
static void main(String[] args) throws Exception {
NenjimHub nenjimHub = new NenjimHubImpl();
/* try { // TODO: Consider if we really need a Main class or we just need to move the main method to NenjimHubImpl?
static void main(String[] args) throws Exception {
NenjimHubImpl nenjimHub = new NenjimHubImpl();
nenjimHub.awaitShutdown();
/* try {
log.info("Auto-starting 2 Nenjim application(s)..."); log.info("Auto-starting 2 Nenjim application(s)...");
log.info(" Starting AssetAZCore..."); log.info(" Starting AssetAZCore...");
//com.r35157.; //com.r35157.;
@@ -1,5 +1,6 @@
package com.r35157.nenjim.hubd.impl.ref; package com.r35157.nenjim.hubd.impl.ref;
import com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl;
import com.r35157.nenjim.hubd.NenjimHub; import com.r35157.nenjim.hubd.NenjimHub;
import com.r35157.nenjim.hubd.journal.Journal; import com.r35157.nenjim.hubd.journal.Journal;
import crypto.r35157.nenjim.NenjimProcess; import crypto.r35157.nenjim.NenjimProcess;
@@ -8,6 +9,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
public class NenjimHubImpl implements NenjimHub { public class NenjimHubImpl implements NenjimHub {
public NenjimHubImpl() throws Exception { public NenjimHubImpl() throws Exception {
@@ -38,19 +40,24 @@ public class NenjimHubImpl implements NenjimHub {
} }
private void startAutoRunProcesses() throws Exception { private void startAutoRunProcesses() throws Exception {
String[] processesToAutoStart = { startJupiterPerpsAlarm(); // TODO: Hardcoded/hacky way to auto-start but good enough for now.
"crypto.r35157.nenjim.NenjimHubSocketAdminAdapter", // TODO: Old but more correct way to auto start plugins - but it is currently broken.
"crypto.r35157.nenjim.NenjimHubRestAdminAdapter", /*
"crypto.r35157.nenjim.NenjimHubRPCAdminAdapter", String[] processesToAutoStart = {
"crypto.r35157.nenjim.SuwimoHub", //"com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl"
"crypto.r35157.nenjim.SodaTaskManager", //"crypto.r35157.nenjim.NenjimHubSocketAdminAdapter",
"crypto.r35157.assetaz.hub.AssetAZHub" //"crypto.r35157.nenjim.NenjimHubRestAdminAdapter",
//"crypto.r35157.nenjim.NenjimHubRPCAdminAdapter",
//"crypto.r35157.nenjim.SuwimoHub",
//"crypto.r35157.nenjim.SodaTaskManager",
//"crypto.r35157.assetaz.hub.AssetAZHub"
}; };
for (String processInterfaceName : processesToAutoStart) { for (String processInterfaceName : processesToAutoStart) {
startProcess(processInterfaceName); startProcess(processInterfaceName);
} }
*/
} }
@Override @Override
@@ -90,7 +97,27 @@ public class NenjimHubImpl implements NenjimHub {
System.out.println("NenjimHub command: 'noop'"); System.out.println("NenjimHub command: 'noop'");
} }
public void awaitShutdown() throws InterruptedException {
shutdownLatch.await();
}
private void startJupiterPerpsAlarm() {
Thread thread = new Thread(() -> {
try {
JupiterPerpsAlarmImpl.main(new String[] {
"--config=conf/alarms.conf"
});
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}, "Nenjim Plugin - JupiterPerpsAlarm");
thread.setDaemon(false);
thread.start();
}
private static final Logger log = LoggerFactory.getLogger(NenjimHubImpl.class); private static final Logger log = LoggerFactory.getLogger(NenjimHubImpl.class);
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private HashMap<Integer, NenjimProcess> processes; private HashMap<Integer, NenjimProcess> processes;
//private StructuredTaskScope.ShutdownOnFailure processesScope; //private StructuredTaskScope.ShutdownOnFailure processesScope;