ErlangDistMoneroClient / _DistConn had WALLET_SERVICE_NAME = "Elixir.Wallet.Service"
hardcoded as the REG_SEND target, ignoring the registered_name read from
production.ini's [app:main] wallet_dist.registered_name.
Smoke-test against production portal failed for hours with
'peer closed after 0/4 bytes' — the bytes WERE arriving at portal, dist
handshake succeeded, but portal had no process registered as
:'Elixir.Wallet.Service' (the actual registered name is
:'Elixir.Wallet.Bridge', because the bridge GenServer forwards from MPS
to wallet@cammy.foxhop.net). REG_SEND to an unregistered name is
silently dropped by the dist driver — exactly the symptom we saw.
Confirmed by direct Python call to :'Elixir.Wallet.Bridge' from the
same MPS host succeeding while the CLI failed; only difference was the
hardcoded target name.
WalletDistConfig.client_kwargs() now includes registered_name.
_DistConn / ErlangDistMoneroClient / open_wallet_conn accept it as
an optional kwarg (defaults to legacy WALLET_SERVICE_NAME for
backwards-compat with callers that don't pass it).
This was NOT a TLS 1.3 bug — earlier debugging hypothesized that but
was misled by the symptom. Locking TLS 1.2 max made the direct test
work because the direct test was already calling the right name.
Bumps version to force the CI artifact to rebuild with the new
source_hash so salt-managed MPS deployments re-fetch env.tar.gz.
Includes (since 1.1.6):
- eae627e crypto_watcher: dist clients for XMR/BTC/LTC/DOGE via erldistpy
- b6268a9 deps: erldistpy >=0.1.6 (PyPI) — drop-in for new dist clients
- 02f55a9 crypto_watcher: wallet_dist config + mps_wallet_dist_health CLI
mps_wallet_dist_health is the standalone CLI used to smoke-test the
TLS-dist transport against wallet@cammy.foxhop.net independent of
the crypto_watcher daemon.
Joining make_post_sell prod to our unsandbox Erlang cluster as a
hidden Python node so the watcher can route wallet RPC through
Wallet.Service on cammy via erldistpy. Two pieces in this commit:
wallet_dist_config.py
Reads wallet_dist.* keys from production.ini's [app:main]:
- enabled
- our_node_name / peer_name / peer_host / registered_name
- cert / key / ca (all three or none — partial config errors)
- cookie_file (path; value read at load time, never logged)
Returns a frozen WalletDistConfig dataclass with .client_kwargs()
that maps to ErlangDist*Client constructor signatures.
wallet_dist_health.py + mps_wallet_dist_health console_script
Single-shot smoke test: load config, open dist connection, call
Wallet.Service.monero_get_height + is_synced, print result. Exit
code 0 = wire works; 2 = config problem; 3 = dist call failed.
Lets ops verify on prod via:
mps_wallet_dist_health /opt/make_post_sell/production.ini
erldistpy imported lazily inside the connection helper — deploys
that stay on direct-HTTP wallet clients don't need erldistpy
resolvable.
11 new tests for wallet_dist_config covering enabled/disabled,
TLS-all-or-none, missing-required-key, unreadable cookie file.
Existing 44 erldist_clients tests still green (total 55 dist-related
tests, plus the 110 existing crypto_watcher tests untouched).
Implement "Stay in the Loop" feature set:
- ShopSubscription model for daily/weekly email digest subscriptions
- Subscribe/verify/unsubscribe views with token-based email verification
- Digest sender console script for cron-based email delivery
- @mention parsing in comments with immediate email notifications
- RSS/Atom autodiscovery link tags in page head
- Shop settings toggle for enabling/disabling subscriptions
- Fix crypto_processor.py UUID slicing error by using shop.uuid_str
- Use clean UUID-based label: mps-shop-{uuid}
- Add account tagging with shop name for human identification
- Update development.ini to use environment variables for payment toggles
- Run black formatter on all Python files
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add download permissions testing to free coupon checkout regression test
- Create separate basic download permissions test with HTML assertions
- Fix database state management by removing manual user deletion in tearDown
- Update version from 1.0.4 to 1.0.5 for new release
- Validate all three download scenarios:
1. Download permissions work correctly (both tests)
2. Download button appears when file exists (mocked is_ready)
3. Download button absent when no file (unmocked behavior)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Dead man switch activated. Good luck everyone, I wish you the best.
- BDFL
new file: .gitignore
new file: MANIFEST.in
new file: Makefile
new file: README.rst
new file: development.ini
new file: journal.rst
new file: make_post_sell/__init__.py
new file: make_post_sell/config_jinja2.py
new file: make_post_sell/lib/__init__.py
new file: make_post_sell/lib/mail.py
new file: make_post_sell/lib/mail_messages.py
new file: make_post_sell/lib/render.py
new file: make_post_sell/models/__init__.py
new file: make_post_sell/models/cart.py
new file: make_post_sell/models/cart_coupon.py
new file: make_post_sell/models/coupon.py
new file: make_post_sell/models/coupon_redemption.py
new file: make_post_sell/models/invoice.py
new file: make_post_sell/models/market.py
new file: make_post_sell/models/meta.py
new file: make_post_sell/models/price.py
new file: make_post_sell/models/product.py
new file: make_post_sell/models/shop.py
new file: make_post_sell/models/shop_search_request.py
new file: make_post_sell/models/stripe_user_shop.py
new file: make_post_sell/models/user.py
new file: make_post_sell/models/user_product.py
new file: make_post_sell/models/user_shop.py
new file: make_post_sell/request_methods.py
new file: make_post_sell/routes.py
new file: make_post_sell/scripts/__init__.py
new file: make_post_sell/scripts/alembic/README
new file: make_post_sell/scripts/alembic/env.py
new file: make_post_sell/scripts/alembic/script.py.mako
new file: make_post_sell/scripts/alembic/versions/15751f5d7a03_add_active_cart_total_in_cents_to_user_.py
new file: make_post_sell/scripts/alembic/versions/24662905655f_create_new_active_shop_id_column_on_.py
new file: make_post_sell/scripts/alembic/versions/3e1e70bfe89d_google_analytics_id.py
new file: make_post_sell/scripts/alembic/versions/4d9da43b0e86_product_visibility_column.py
new file: make_post_sell/scripts/alembic/versions/5c40dfb56179_shop_ribbon_columns.py
new file: make_post_sell/scripts/alembic/versions/6da97f2f913f_add_updated_timestamp_to_cart.py
new file: make_post_sell/scripts/alembic/versions/96f851cbab01_terms_and_privacy.py
new file: make_post_sell/scripts/alembic/versions/ab7338eaedf1_add_price_in_cents_column.py
new file: make_post_sell/scripts/alembic/versions/af28cc35ed6f_product_bundles.py
new file: make_post_sell/scripts/alembic/versions/b0d5604d9255_add_active_cart_count_to_user_table.py
new file: make_post_sell/scripts/alembic/versions/bfc4195a7f56_product_is_sellable_coluumn.py
new file: make_post_sell/scripts/alembic/versions/c4d5566ec87d_coupon_stackable_and_couponredemption.py
new file: make_post_sell/scripts/alembic/versions/e12bbe39d87e_cart_coupon_table.py
new file: make_post_sell/scripts/alembic/versions/eadd0d69e133_shop_ribbon_text_color.py
new file: make_post_sell/scripts/alembic/versions/f3aa57777025_coupon_table.py
new file: make_post_sell/scripts/alembic/versions/f882f2255cd1_add_stripe_secret_and_public_key_to_shop.py
new file: make_post_sell/scripts/alembic/versions/fd9f7e2f2b78_user_full_name.py
new file: make_post_sell/scripts/initialize_db.py
new file: make_post_sell/scripts/pshell.py
new file: make_post_sell/static/css/alerts.css
new file: make_post_sell/static/css/common.css
new file: make_post_sell/static/css/lib.css
new file: make_post_sell/static/css/mps.css
new file: make_post_sell/static/favicon.ico
new file: make_post_sell/static/js/custom.js
new file: make_post_sell/static/js/jquery-3.4.1.min.js
new file: make_post_sell/static/js/jquery.min.js
new file: make_post_sell/static/makepostsell-3.png
new file: make_post_sell/static/makepostsell.png
new file: make_post_sell/static/mps.png
new file: make_post_sell/templates/actions_new.j2
new file: make_post_sell/templates/actions_view.j2
new file: make_post_sell/templates/base.j2
new file: make_post_sell/templates/billing.j2
new file: make_post_sell/templates/cart.j2
new file: make_post_sell/templates/cart_checkout.j2
new file: make_post_sell/templates/carts.j2
new file: make_post_sell/templates/content.j2
new file: make_post_sell/templates/content_new.j2
new file: make_post_sell/templates/coupon.j2
new file: make_post_sell/templates/coupon_new.j2
new file: make_post_sell/templates/coupons.j2
new file: make_post_sell/templates/home.j2
new file: make_post_sell/templates/join-or-log-in.j2
new file: make_post_sell/templates/markup_editor.j2
new file: make_post_sell/templates/markup_renderer.j2
new file: make_post_sell/templates/product.j2
new file: make_post_sell/templates/product_edit.j2
new file: make_post_sell/templates/product_new.j2
new file: make_post_sell/templates/products.j2
new file: make_post_sell/templates/shop.j2
new file: make_post_sell/templates/shop_about.j2
new file: make_post_sell/templates/shop_new.j2
new file: make_post_sell/templates/shop_products.j2
new file: make_post_sell/templates/shop_settings.j2
new file: make_post_sell/templates/shops.j2
new file: make_post_sell/templates/snippets/csrf.j2
new file: make_post_sell/templates/snippets/flash-alerts.j2
new file: make_post_sell/templates/snippets/google-analytics.j2
new file: make_post_sell/templates/snippets/optional-javascript.j2
new file: make_post_sell/templates/snippets/ribbon.j2
new file: make_post_sell/templates/snippets/search.j2
new file: make_post_sell/templates/snippets/stripe.j2
new file: make_post_sell/templates/update-card.j2
new file: make_post_sell/templates/user_settings.j2
new file: make_post_sell/tests/__init__.py
new file: make_post_sell/tests/test_functional.py
new file: make_post_sell/tests/test_models.py
new file: make_post_sell/views/__init__.py
new file: make_post_sell/views/actions.py
new file: make_post_sell/views/authentication.py
new file: make_post_sell/views/billing.py
new file: make_post_sell/views/cart.py
new file: make_post_sell/views/content.py
new file: make_post_sell/views/coupon.py
new file: make_post_sell/views/misc.py
new file: make_post_sell/views/product.py
new file: make_post_sell/views/shop.py
new file: make_post_sell/views/user.py
new file: pytest.ini
new file: requirements-dev.txt
new file: requirements-prod.txt
new file: requirements-test.txt
new file: requirements.py3.txt
new file: requirements.txt
new file: setup.py
new file: test.ini