Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ services:
stop_grace_period: 30s
volumes:
- /mnt/hdd/mynode/canary:/app/data
env_file:
- /mnt/hdd/mynode/canary/canary.env
environment:
CANARY_DATA_DIR: /app/data
CANARY_ELECTRUM_URL: tcp://127.0.0.1:50001
Expand All @@ -24,4 +26,4 @@ services:
- backend
environment:
API_URL: http://127.0.0.1:3004
PORT: "3005"
PORT: "3005"
6 changes: 4 additions & 2 deletions rootfs/standard/usr/share/mynode_apps/canary/canary.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"Get instant notifications when your bitcoins move via ntfy push notifications.",
"Features include: transaction monitoring, RBF/CPFP detection, balance alerts, and deep wallet scanning."
],
"latest_version": "v1.4.0",
"latest_version": "v1.5.0",
"supported_archs": ["amd64", "arm64"],
"download_skip": true,
"requires_docker_image_installation": true,
Expand All @@ -29,7 +29,9 @@
"app_tile_name": "Canary",
"app_tile_running_status_text": "Monitoring",
"app_tile_default_status_text": "Wallet Monitor",
"app_tile_button_open_app_directly": true,
"app_tile_button_text": "Info",
"app_tile_button_href": "/app/canary/info",
"app_tile_button_open_app_directly": false,
"can_uninstall": true,
"can_reinstall": true,
"can_enable_disable": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ set -e

echo "==================== INSTALLING APP ===================="

VERSION="${VERSION:-v1.5.0}"

mkdir -p /opt/mynode/canary || true
mkdir -p /mnt/hdd/mynode/canary || true

Expand Down
30 changes: 27 additions & 3 deletions rootfs/standard/usr/share/mynode_apps/canary/scripts/pre_canary.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,33 @@

set -e

# Keep the compose file in sync with the packaged app data.
DATA_DIR="/mnt/hdd/mynode/canary"
ADMIN_PASSWORD_FILE="$DATA_DIR/admin_password"
JWT_SECRET_FILE="$DATA_DIR/jwt_secret"
ENV_FILE="$DATA_DIR/canary.env"

generate_secret() {
local length="$1"
tr -dc A-Za-z0-9 < /dev/urandom | head -c "$length"
}

cp -f app_data/docker-compose.yml docker-compose.yml

# Ensure data directory exists before starting.
mkdir -p /mnt/hdd/mynode/canary
chown -R bitcoin:bitcoin /mnt/hdd/mynode/canary
mkdir -p "$DATA_DIR"

if [ ! -s "$ADMIN_PASSWORD_FILE" ]; then
generate_secret 32 > "$ADMIN_PASSWORD_FILE"
fi

if [ ! -s "$JWT_SECRET_FILE" ]; then
generate_secret 64 > "$JWT_SECRET_FILE"
fi

cat > "$ENV_FILE" <<EOF
CANARY_SELF_HOSTED_ADMIN_PASSWORD=$(cat "$ADMIN_PASSWORD_FILE")
JWT_SECRET=$(cat "$JWT_SECRET_FILE")
EOF

chown -R bitcoin:bitcoin "$DATA_DIR"
chmod 600 "$ADMIN_PASSWORD_FILE" "$JWT_SECRET_FILE" "$ENV_FILE"
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ echo "==================== UNINSTALLING APP ===================="
cp -f app_data/docker-compose.yml docker-compose.yml 2>/dev/null || true
/usr/local/bin/docker-compose down --remove-orphans 2>/dev/null || true


remove_docker_images_by_name "canary-backend"
remove_docker_images_by_name "canary-frontend"
remove_docker_images_by_name "schjonhaug/canary-backend"
remove_docker_images_by_name "schjonhaug/canary-frontend"

rm -rf /mnt/hdd/mynode/canary

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@
from user_management import check_logged_in
from application_info import get_application, get_application_status, get_application_status_color
from device_info import read_ui_settings
import os


mynode_canary = Blueprint("mynode_canary", __name__)

CANARY_PASSWORD_FILE = "/mnt/hdd/mynode/canary/admin_password"


def get_canary_password():
if not os.path.isfile(CANARY_PASSWORD_FILE):
return ""
with open(CANARY_PASSWORD_FILE, "r") as password_file:
return password_file.read().strip()


@mynode_canary.route("/info")
def canary_page():
Expand All @@ -21,5 +31,7 @@ def canary_page():
"app_status": app_status,
"app_status_color": app_status_color,
"app": app,
"canary_username": "admin@local",
"canary_password": get_canary_password(),
}
return render_template("/app/generic_app.html", **template_data)
return render_template("/app/canary/canary.html", **template_data)
210 changes: 210 additions & 0 deletions rootfs/standard/usr/share/mynode_apps/canary/www/templates/canary.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<!DOCTYPE html lang="en">
<head>
<title>{{app.name}}</title>
{% include 'includes/head.html' %}

<script src="{{ url_for('static', filename='js/manage_apps.js')}}"></script>

<style>
.canary_credentials_value {
font-family: monospace;
word-break: break-all;
}
.canary_credentials_missing {
color: #888;
}
</style>

<script>
$(document).ready(function() {
$("#show_canary_password").on("click", function() {
$("#show_canary_password").hide(0);
$("#canary_password").show();
$("#copy_canary_password").show();
});

$("#copy_canary_password").on("click", function() {
var password = "{{canary_password}}";
function showCopied() {
showAlertPopup("alert_popup", "<b>Password copied!</b>");
}
function showFailed() {
showAlertPopup("alert_popup", "<b>Password copy failed!</b>");
}

if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(password).then(showCopied, showFailed);
return;
}

var input = document.createElement("input");
input.value = password;
document.body.appendChild(input);
input.select();
try {
document.execCommand("copy") ? showCopied() : showFailed();
} catch (e) {
showFailed();
}
document.body.removeChild(input);
});
});
</script>
</head>
<body>
{% include 'includes/logo_header.html' %}
<div class="mynode_top_left_div">
<a href="/"><img class="mynode_nav_icon" src="{{ url_for('static', filename="images/home.png")}}"/></a>
</div>

<div class="main_header">{{app.name}}</div>
<br/>

<div class="app_page_container">
<div class="app_page_block_header">&nbsp;</div>
<div class="app_page_block_contents">

<div class="app_page_block_contents_left">
<img class="app_page_icon" src="{{ url_for('static', filename="images/app_icons/")}}{{app.short_name}}.png"/>
{% if not app.hide_status_icon %}
<div class="app_page_status_bar {{app_status_color}}"></div>
{% endif %}
<p style="font-size: 14px; text-align: center;">{{app_status}}</p>

<br/>

{% if not app.is_installed %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button install_button" onclick="install('{{ app.name }}', '{{ app.short_name }}');">Install</button>
{% else %}

{% if app.is_enabled and app.app_page_show_open_button %}
{% if app.http_port != "" or app.https_port != "" %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="open_app_in_new_tab('{{app.http_port}}', '{{app.https_port}}', false, '{{app.tor_address}}')">Open</button>

<div class="divider button_divider"></div>
{% endif %}
{% endif %}

{% if app.is_enabled %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="restart_app_via_api('{{ app.name }}', '{{ app.short_name }}');">Restart</button>

{% if app.is_enabled and app.data_manageable %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="reset_data_folder_via_api('{{ app.name }}', '{{ app.short_name }}');">Reset Data</button>
{% endif %}

{% for btn in app.app_page_additional_buttons %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button"
{% if btn.href is defined and btn.href != "" %}
onclick="window.location='{{btn.href}}'"
{% elif btn.onclick is defined and btn.onclick != "" %}
onclick="{{btn.onclick|safe}}"
{% endif %}
>{{btn.title}}</button>
{% endfor %}

<div class="divider button_divider"></div>
{% endif %}

{% if app.can_enable_disable %}
{% if not app.is_enabled %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="toggleEnabled('{{app.short_name}}', '{{app.name}}', true)">Enable</button>
{% else %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="toggleEnabled('{{app.short_name}}', '{{app.name}}', false)">Disable</button>
{% endif %}
{% endif %}
{% endif %}

</div>

<div class="app_page_block_contents_right">
<div class="app_page_block_contents_heading">
<div class="info-page-block">Info</div>
</div>
<div class="app_page_block_contents_text">
<table class="info_table" style="font-size: 12px; margin-left: 10px">
<tr>
<th>Installed Version</th>
<td>
{% if app.is_installed %}
{{app.current_version}}
{% else %}
Not Installed
{% endif %}
</td>
</tr>
{% if app.author.name is defined %}
<tr>
<th>Author</th>
<td>
{% if app.author.link is defined and app.author.link != "" %}
<a href="{{app.author.link}}" target="_blank">{{app.author.name}}</a>
{% else %}
{{app.author.name}}
{% endif %}
</td>
</tr>
{% endif %}
{% if app.website.name is defined and app.website.link is defined %}
<tr>
<th>Website</th>
<td>
<a href="{{app.website.link}}" target="_blank">{{app.website.name}}</a>
</td>
</tr>
{% endif %}
</table>
</div>

<div class="app_page_block_contents_heading">
<div class="info-page-block">Credentials</div>
</div>
<div class="app_page_block_contents_text">
<table class="info_table" style="font-size: 12px; margin-left: 10px">
<tr>
<th>Username</th>
<td><span class="canary_credentials_value">{{canary_username}}</span></td>
</tr>
<tr>
<th>Password</th>
<td>
{% if canary_password %}
<span id="canary_password" class="canary_credentials_value" style="display: none; margin: 0;">{{canary_password}}</span>
<span id="copy_canary_password" class="ui-icon ui-icon-copy" style="cursor: pointer; display: none;" title="Copy Password"></span>
<a id="show_canary_password" class="ui-button ui-widget ui-corner-all mynode_button_small" style="width: 100px;">show password</a>
{% else %}
<span class="canary_credentials_missing">Not generated yet</span>
{% endif %}
</td>
</tr>
</table>
</div>

{% if app.app_page_content is defined and app.app_page_content|length > 0 %}
{% for section in app.app_page_content %}
<div class="app_page_block_contents_heading">
<div class="info-page-block">{{section.heading}}</div>
</div>
<div class="app_page_block_contents_text">
{% for parapraph in section.content %}
<p>{{parapraph}}</p>
{% endfor %}
</div>
{% endfor %}
{% endif %}

</div>

</div>
</div>

<div id="confirm-dialog"></div>

<div id="loading_spinner_overlay" class="loading_spinner_overlay" style="display:none;">
<img id="loading_spinner" class="loading_image" src="{{ url_for('static', filename="images/loading.gif")}}"/>
<br/>
<span id="loading_spinner_message">Loading...</span>
</div>

{% include 'includes/footer.html' %}
</body>
</html>