Compare commits
1
Commits
master
..
7a57d3b4c5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a57d3b4c5
|
+1
-1
Submodule nodes updated: 90c15e40f8...5fd2e23a01
@@ -1,199 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Manually record an incoming Lightning payment to credit a lndhub.go user account.
|
||||
#
|
||||
# Mirrors the canonical incoming-settlement path in lib/service/invoicesubscription.go
|
||||
# (ProcessInvoiceUpdate): inserts a settled `invoices` row plus a `transaction_entries`
|
||||
# row crediting the user's `current` account and debiting their `incoming` account
|
||||
# (which is exempt from the non-negative check_balance() trigger, so it may go negative).
|
||||
#
|
||||
# Usage:
|
||||
# ./gitno/credit-account.sh --env-file <path/to/.env> \
|
||||
# --login <login> --sats <N> [--memo "manual credit"]
|
||||
#
|
||||
# The --env-file is the lndhub.go .env file (the same one loaded by godotenv in
|
||||
# cmd/server/main.go). Only DATABASE_URI is read from it; the value from the file
|
||||
# ALWAYS takes precedence over any DATABASE_URI already present in the environment.
|
||||
#
|
||||
# Requires: psql. The whole operation runs in one transaction; ON_ERROR_STOP=1
|
||||
# aborts on any error so the tx is rolled back and psql exits non-zero.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: credit-account.sh --env-file <path/to/.env> --login <login> --sats <N> [--memo "..."]
|
||||
|
||||
Options:
|
||||
--env-file Path to the lndhub.go .env file (required). Only DATABASE_URI is
|
||||
read from it; the file value always wins over the environment.
|
||||
--login User login (required)
|
||||
--sats Amount in satoshis to credit (required, positive integer)
|
||||
--memo Memo for the recorded invoice (default: "manual credit")
|
||||
-h, --help
|
||||
EOF
|
||||
exit "${1:-0}"
|
||||
}
|
||||
|
||||
env_file=""
|
||||
login=""
|
||||
sats=""
|
||||
memo="manual credit"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--env-file) env_file="$2"; shift 2 ;;
|
||||
--login) login="$2"; shift 2 ;;
|
||||
--sats) sats="$2"; shift 2 ;;
|
||||
--memo) memo="$2"; shift 2 ;;
|
||||
-h|--help) usage 0 ;;
|
||||
*) echo "Unknown argument: $1" >&2; usage 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$env_file" ]] || { echo "Error: --env-file is required" >&2; usage 1; }
|
||||
[[ -n "$login" ]] || { echo "Error: --login is required" >&2; usage 1; }
|
||||
[[ -n "$sats" ]] || { echo "Error: --sats is required" >&2; usage 1; }
|
||||
[[ "$sats" =~ ^[1-9][0-9]*$ ]] || { echo "Error: --sats must be a positive integer" >&2; usage 1; }
|
||||
|
||||
# Sanity-check psql is available.
|
||||
command -v psql >/dev/null || { echo "Error: psql not found in PATH" >&2; exit 1; }
|
||||
|
||||
# Extract a single KEY=VALUE entry from a .env file, matching godotenv semantics:
|
||||
# - skips blank lines and lines whose first non-whitespace char is '#'
|
||||
# - tolerates an optional leading 'export '
|
||||
# - case-insensitive key match
|
||||
# - strips one pair of surrounding matching quotes (" or ') from the value
|
||||
# Writes the result to stdout; empty output means "not found".
|
||||
extract_env_key() {
|
||||
local file="$1" key="$2" line k v
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
# Trim leading whitespace; skip blanks and comments
|
||||
line="${line#"${line%%[![:space:]]*}"}"
|
||||
[[ -z "$line" || "${line:0:1}" == "#" ]] && continue
|
||||
# Strip optional 'export ' prefix
|
||||
[[ "$line" == export\ * ]] && line="${line#export }"
|
||||
# Split on the first '='
|
||||
[[ "$line" != *=* ]] && continue
|
||||
k="${line%%=*}"
|
||||
v="${line#*=}"
|
||||
# Case-insensitive key compare
|
||||
if [[ "${k,,}" == "${key,,}" ]]; then
|
||||
# Strip one pair of surrounding matching single or double quotes
|
||||
if [[ ${#v} -ge 2 ]]; then
|
||||
if [[ "${v:0:1}" == '"' && "${v: -1}" == '"' ]]; then v="${v:1:-1}";
|
||||
elif [[ "${v:0:1}" == "'" && "${v: -1}" == "'" ]]; then v="${v:1:-1}"; fi
|
||||
fi
|
||||
printf '%s\n' "$v"
|
||||
return 0
|
||||
fi
|
||||
done < "$file"
|
||||
return 1
|
||||
}
|
||||
|
||||
[[ -r "$env_file" ]] || { echo "Error: --env-file not readable: $env_file" >&2; exit 1; }
|
||||
|
||||
# DATABASE_URI is sourced exclusively from --env-file; the file value always wins
|
||||
# over any DATABASE_URI already present in the environment.
|
||||
if ! DATABASE_URI="$(extract_env_key "$env_file" DATABASE_URI)"; then
|
||||
echo "Error: DATABASE_URI not found in $env_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -n "$DATABASE_URI" ]] || { echo "Error: DATABASE_URI is empty in $env_file" >&2; exit 1; }
|
||||
|
||||
case "$DATABASE_URI" in
|
||||
postgres://*|postgresql://*) ;;
|
||||
*) echo "Error: DATABASE_URI must start with postgres:// or postgresql:// (got: $DATABASE_URI)" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
psql "$DATABASE_URI" \
|
||||
-v ON_ERROR_STOP=1 \
|
||||
-v login="$login" \
|
||||
-v sats="$sats" \
|
||||
-v memo="$memo" \
|
||||
<<'SQL'
|
||||
\set ON_ERROR_STOP on
|
||||
\echo Resolving user, accounts, and recording the credit in one transaction...
|
||||
|
||||
-- Pass the CLI args into server-side GUCs so the DO block (dollar-quoted, where
|
||||
-- psql :var substitution does NOT happen) can read them via current_setting().
|
||||
-- These SETs run outside any dollar-quote, so :'login'/:sats/:'memo' are substituted
|
||||
-- by psql as a properly-quoted SQL literal / bare token here.
|
||||
SET app.lndhub_login = :'login';
|
||||
SET app.lndhub_sats = :sats;
|
||||
SET app.lndhub_memo = :'memo';
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Look up user + accounts, validate, and insert the invoice + ledger entry.
|
||||
-- Raises an exception (aborting the tx, psql exits non-zero via ON_ERROR_STOP)
|
||||
-- with a helpful message if the user or either account is missing.
|
||||
DO $$
|
||||
DECLARE
|
||||
v_uid bigint;
|
||||
v_curr bigint;
|
||||
v_inc bigint;
|
||||
v_inv_id bigint;
|
||||
v_login text := current_setting('app.lndhub_login');
|
||||
v_sats bigint := current_setting('app.lndhub_sats')::bigint;
|
||||
v_memo text := current_setting('app.lndhub_memo');
|
||||
BEGIN
|
||||
SELECT id INTO v_uid FROM users WHERE login = v_login;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'no user found with login = %', v_login;
|
||||
END IF;
|
||||
|
||||
SELECT id INTO v_curr FROM accounts WHERE user_id = v_uid AND type = 'current';
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'user % has no current account', v_uid;
|
||||
END IF;
|
||||
|
||||
SELECT id INTO v_inc FROM accounts WHERE user_id = v_uid AND type = 'incoming';
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'user % has no incoming account', v_uid;
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'user_id=% current_acct=% incoming_acct=% amount=% sats',
|
||||
v_uid, v_curr, v_inc, v_sats;
|
||||
|
||||
-- Settled incoming invoice. Constraints honored:
|
||||
-- * destination_pubkey_hex NOT NULL -> 66-hex dummy
|
||||
-- * settled => preimage NOT NULL (check_primage_exists)
|
||||
-- * fresh r_hash/preimage per run keeps rows distinct
|
||||
-- * uses gen_random_uuid() (built-in core since PG13, no pgcrypto needed);
|
||||
-- 32 hex chars is sufficient for a dummy identifier
|
||||
INSERT INTO invoices
|
||||
(type, user_id, amount, memo, destination_pubkey_hex, r_hash, preimage,
|
||||
state, settled_at, created_at, internal)
|
||||
VALUES
|
||||
('incoming', v_uid, v_sats, v_memo,
|
||||
'000000000000000000000000000000000000000000000000000000000000000000',
|
||||
replace(gen_random_uuid()::text, '-', ''),
|
||||
replace(gen_random_uuid()::text, '-', ''),
|
||||
'settled', now(), now(), false)
|
||||
RETURNING id INTO v_inv_id;
|
||||
|
||||
RAISE NOTICE 'invoice_id=%', v_inv_id;
|
||||
|
||||
-- Ledger entry: credit current (+amount), debit incoming (-amount).
|
||||
-- * debit_account_id != credit_account_id (current != incoming) -> check_not_same_account ok
|
||||
-- * debit on `incoming` is exempt from check_balance() -> may go negative
|
||||
-- * fresh invoice_id keeps unique_tx_entry_tuple satisfied
|
||||
INSERT INTO transaction_entries
|
||||
(user_id, invoice_id, credit_account_id, debit_account_id, amount, entry_type, created_at)
|
||||
VALUES
|
||||
(v_uid, v_inv_id, v_curr, v_inc, v_sats, 'incoming', now());
|
||||
|
||||
RAISE NOTICE 'credited % sats to user % (login=%)', v_sats, v_uid, v_login;
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- Resulting current-account balance (same query as CurrentUserBalance in
|
||||
-- lib/service/user.go: sum over account_ledgers for the user's current account).
|
||||
SELECT
|
||||
(SELECT sum(al.amount)
|
||||
FROM account_ledgers al
|
||||
JOIN accounts a ON a.id = al.account_id
|
||||
WHERE a.user_id = (SELECT id FROM users WHERE login = current_setting('app.lndhub_login'))
|
||||
AND a.type = 'current') AS new_balance_sat;
|
||||
SQL
|
||||
@@ -19,7 +19,7 @@ server {
|
||||
ssl_certificate <%= @ssl_cert %>;
|
||||
ssl_certificate_key <%= @ssl_key %>;
|
||||
|
||||
location ~ "^/(?<sha256>[a-f0-9]{64})(\.[a-zA-Z0-9]+)?$" {
|
||||
location ~ ^/(?<sha256>[a-f0-9]{64})(\.[a-zA-Z0-9]+)?$ {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
@@ -29,12 +29,6 @@ template "/etc/spamassassin/local.cf" do
|
||||
notifies :restart, "service[spamassassin]", :delayed
|
||||
end
|
||||
|
||||
template "/etc/spamassassin/kosmos.cf" do
|
||||
source "spamassassin_kosmos.cf.erb"
|
||||
mode 0644
|
||||
notifies :restart, "service[spamassassin]", :delayed
|
||||
end
|
||||
|
||||
service "spamassassin" do
|
||||
action [:enable, :start]
|
||||
end
|
||||
|
||||
@@ -30,4 +30,4 @@ PIDFILE="/var/run/spamd.pid"
|
||||
# Cronjob
|
||||
# Set to anything but 0 to enable the cron job to automatically update
|
||||
# spamassassin's rules on a nightly basis
|
||||
CRON=1
|
||||
CRON=0
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
###########################################################################
|
||||
# Kosmos custom SpamAssassin rules
|
||||
#
|
||||
# The rules below are designed to be individually weak but combine via meta
|
||||
# rules into strong, low-FP signals.
|
||||
###########################################################################
|
||||
|
||||
# --- Individual signals --------------------------------------------------
|
||||
|
||||
# URLs of the form https://////////////... (3+ slashes after the scheme).
|
||||
# Legitimate mailers never produce this; it is an obfuscation artefact.
|
||||
rawbody KOSMOS_MULTI_SLASH_URL /https?:\/{3,}/
|
||||
describe KOSMOS_MULTI_SLASH_URL URL with three or more consecutive slashes
|
||||
|
||||
# Authoring-tool fingerprint left in the HTML by the spam toolchain.
|
||||
body KOSMOS_MSHTML_11_9600 /MSHTML 11\.00\.9600\.17037/
|
||||
describe KOSMOS_MSHTML_11_9600 HTML generated by MSHTML 11.00.9600.17037
|
||||
|
||||
# Display name pattern used by the campaign.
|
||||
header KOSMOS_FROM_LUXURY_GOODS From:name =~ /Luxury (Watches|Bags|Handbags|Timepieces)\b/i
|
||||
describe KOSMOS_FROM_LUXURY_GOODS From display name advertises luxury goods
|
||||
|
||||
# Base64-encoded unsubscribe links: return.php?p=<long base64>
|
||||
uri KOSMOS_RETURN_PHP_B64 /return\.php\?p=[A-Za-z0-9+\/=%]{20,}/
|
||||
describe KOSMOS_RETURN_PHP_B64 Base64-encoded return.php unsubscribe link
|
||||
|
||||
# Fabricated "security" headers injected to evade heuristic filters.
|
||||
# No legitimate MTA or mailing-list manager emits these.
|
||||
header KOSMOS_FAKE_HDR_PHISHSIM exists:X-PhishSimulator-Mode
|
||||
header KOSMOS_FAKE_HDR_DECEPTION exists:X-Deception-Asset-Type
|
||||
header KOSMOS_FAKE_HDR_OBFUSCATION exists:X-Obfuscation-Trace-ID
|
||||
header KOSMOS_FAKE_HDR_QUARANTINE exists:X-Quarantine-Reason-Code
|
||||
header KOSMOS_FAKE_HDR_TRUST exists:X-Behavioral-Trust-Index
|
||||
|
||||
# --- Scores for individual signals --------------------------------------
|
||||
score KOSMOS_MULTI_SLASH_URL 2.0
|
||||
score KOSMOS_MSHTML_11_9600 1.2
|
||||
score KOSMOS_FROM_LUXURY_GOODS 0.5
|
||||
score KOSMOS_RETURN_PHP_B64 1.5
|
||||
score KOSMOS_FAKE_HDR_PHISHSIM 1.0
|
||||
score KOSMOS_FAKE_HDR_DECEPTION 1.0
|
||||
score KOSMOS_FAKE_HDR_OBFUSCATION 1.0
|
||||
score KOSMOS_FAKE_HDR_QUARANTINE 1.0
|
||||
score KOSMOS_FAKE_HDR_TRUST 1.0
|
||||
|
||||
# --- Meta rules ----------------------------------------------------------
|
||||
|
||||
# Core campaign signature: luxury-goods From name + MSHTML fingerprint +
|
||||
# HTML-only body. Covers the bulk of the campaign corpus.
|
||||
meta KOSMOS_LUXURY_SPAM_CAMPAIGN (KOSMOS_FROM_LUXURY_GOODS && KOSMOS_MSHTML_11_9600 && MIME_HTML_ONLY)
|
||||
describe KOSMOS_LUXURY_SPAM_CAMPAIGN Luxury-goods From + MSHTML 11.00.9600 + HTML-only
|
||||
score KOSMOS_LUXURY_SPAM_CAMPAIGN 3.5
|
||||
|
||||
# Luxury-goods From name combined with a suspicious URI signal or a
|
||||
# Spamhaus-listed relay.
|
||||
meta KOSMOS_LUXURY_SPAM_URI (KOSMOS_FROM_LUXURY_GOODS && (KOSMOS_MULTI_SLASH_URL || KOSMOS_RETURN_PHP_B64 || RCVD_IN_SBL_CSS))
|
||||
describe KOSMOS_LUXURY_SPAM_URI Luxury-goods From + suspicious URI or SBL relay
|
||||
score KOSMOS_LUXURY_SPAM_URI 2.5
|
||||
|
||||
# Two or more fabricated "security" headers. Genuine mail never carries
|
||||
# these; their presence indicates a header-injection evasion kit.
|
||||
meta KOSMOS_FAKE_SECURITY_HEADERS (KOSMOS_FAKE_HDR_PHISHSIM + KOSMOS_FAKE_HDR_DECEPTION + KOSMOS_FAKE_HDR_OBFUSCATION + KOSMOS_FAKE_HDR_QUARANTINE + KOSMOS_FAKE_HDR_TRUST >= 2)
|
||||
describe KOSMOS_FAKE_SECURITY_HEADERS Two or more fabricated security headers
|
||||
score KOSMOS_FAKE_SECURITY_HEADERS 4.0
|
||||
@@ -16,37 +16,6 @@ whitelist_auth <%= @whitelist_auth %>
|
||||
# _CONTACTADDRESS_ in the report template)
|
||||
report_contact <%= @report_contact %>
|
||||
|
||||
###########################################################################
|
||||
# Kosmos custom score overrides
|
||||
#
|
||||
# The Validity (Return Path / SenderScore) "certified sender" whitelists
|
||||
# (RCVD_IN_VALIDITY_CERTIFIED, RCVD_IN_VALIDITY_SAFE) hand out up to -5.0
|
||||
# of credit to sending IPs. These lists are commercially gamed and
|
||||
# routinely award -5.0 to IPs that are simultaneously listed on Spamhaus
|
||||
# SBL-CSS, SpamCop, MSPIKE and Validity's own RPBL. Neutralise them.
|
||||
###########################################################################
|
||||
score RCVD_IN_VALIDITY_CERTIFIED 0
|
||||
score RCVD_IN_VALIDITY_SAFE 0
|
||||
|
||||
###########################################################################
|
||||
# Bayes hardening
|
||||
###########################################################################
|
||||
use_bayes 1
|
||||
bayes_auto_learn 1
|
||||
|
||||
# Do not let Bayes learn from SpamAssassin's own result headers or from
|
||||
# Authentication-Results, which leak signal about prior scoring runs.
|
||||
bayes_ignore_header X-Spam-Flag
|
||||
bayes_ignore_header X-Spam-Status
|
||||
bayes_ignore_header X-Spam-Level
|
||||
bayes_ignore_header X-Spam-Checker-Version
|
||||
bayes_ignore_header Authentication-Results
|
||||
|
||||
# Only learn ham when the message is clearly clean, and only learn spam
|
||||
# when it is clearly spam. The defaults (0.1 / 6.0) let marginally-spam
|
||||
# or marginally-ham messages poison the database.
|
||||
bayes_auto_learn_threshold_nonspam -1.0
|
||||
bayes_auto_learn_threshold_spam 8.0
|
||||
|
||||
# Add *****SPAM***** to the Subject header of spam e-mails
|
||||
#
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Recipe:: default
|
||||
#
|
||||
|
||||
node.normal['openresty']['log_formats']['json'] = '{"ip":"$remote_addr","time":"$time_local","host":"$host","method":"$request_method","uri":"$uri","status":$status,"size":$body_bytes_sent,"referer":"$http_referer","upstream_addr":"$upstream_addr","upstream_response_time":"$upstream_response_time","upstream_cache_status":"$upstream_cache_status","ua":"$http_user_agent"}'
|
||||
node.normal['openresty']['log_formats']['json'] = '{"ip":"$remote_addr","time":"$time_local","host":"$host","method":"$request_method","uri":"$uri","status":$status,"size":$body_bytes_sent,"referer":"$http_referer","upstream_addr":"$upstream_addr","upstream_response_time":"$upstream_response_time","ua":"$http_user_agent"}'
|
||||
|
||||
# Install openresty from official packages
|
||||
include_recipe 'openresty::apt_package'
|
||||
|
||||
Reference in New Issue
Block a user