531 Commits

Author SHA1 Message Date
naturallaw777 f9e1257ebf Nixpkgs update, nix-bitcoin-update 2026-05-21 17:01:34 -05:00
naturallaw777 fe8b1023a6 Nixpkgs and NixBitcoin Update 2026-05-14 10:04:41 -05:00
naturallaw777 42e8aaf70c updated to block rxrpc 2026-05-08 15:18:13 -05:00
naturallaw777 98c701195a nixpkg update 2026-05-08 08:02:11 -05:00
naturallaw777 5b052e8fc9 nixpkgs update 2026-04-30 15:05:40 -05:00
naturallaw777 ca9dbb3cc4 updated readme and update license 2026-04-09 19:30:56 -05:00
naturallaw777 4dea3443b2 fixes sync 2026-03-28 11:37:49 -05:00
naturallaw777 5bee5ad99b updated custom-add-on 2026-03-25 16:53:43 -05:00
naturallaw777 ded0df32e7 updated custom-add-on 2026-03-25 16:35:29 -05:00
naturallaw777 3db8cc8bde updated custom-add-on 2026-03-25 16:30:30 -05:00
naturallaw777 cbe3c6328a updated custom-add-on 2026-03-25 16:15:08 -05:00
naturallaw777 989fcc80e5 updated custom-add-on 2026-03-25 16:12:46 -05:00
naturallaw777 8377c221ab typos readme 2026-03-25 15:31:37 -05:00
naturallaw777 69f8788f5e updated readme 2026-03-25 15:30:47 -05:00
naturallaw777 70cda3fcda updated readme 2026-03-25 15:27:22 -05:00
naturallaw777 f32d117b70 updated readme 2026-03-25 15:19:04 -05:00
naturallaw777 d1047db192 updated readme 2026-03-25 14:53:18 -05:00
naturallaw777 d4faa70e6a typo readme 2026-03-25 14:38:09 -05:00
naturallaw777 d68b4be8d3 updated readme 2026-03-25 14:35:00 -05:00
naturallaw777 b703e058e7 updated readme 2026-03-25 14:16:05 -05:00
naturallaw777 115c2b7056 updated custom nix 2026-03-25 13:41:46 -05:00
naturallaw777 51169ff51c retooling rdp.nix 2026-03-25 13:31:18 -05:00
naturallaw777 03ecb65aca fixed rdp.nix 2026-03-25 13:19:31 -05:00
naturallaw777 a8022d200f fixed rdp.nix 2026-03-25 13:16:30 -05:00
naturallaw777 b0d574e55e added more features rdp.nix 2026-03-25 13:12:44 -05:00
naturallaw777 919bdba7c4 retooled rdp.nix 2026-03-25 13:06:23 -05:00
naturallaw777 2c15341d35 retooled rdp.nix 2026-03-25 13:01:10 -05:00
naturallaw777 b513447be8 updated rdp.nix 2026-03-25 12:24:00 -05:00
naturallaw777 3333e23806 Nixpkgs Update 2026-03-25 12:12:02 -05:00
naturallaw777 634e92b4a7 optimized both flake.nix 2026-03-25 12:01:26 -05:00
naturallaw777 281d2a8523 optimzed flake.nix 2026-03-25 11:54:18 -05:00
naturallaw777 cf1c6c3ad3 updated haven.nix error code 2026-03-25 11:49:04 -05:00
naturallaw777 1dc0df2004 updated haven.nix 2026-03-25 11:45:39 -05:00
naturallaw777 d73ebcaa68 updated haven.nix 2026-03-25 11:36:55 -05:00
naturallaw777 2bc4791d97 updated element-calling 2026-03-25 11:17:19 -05:00
naturallaw777 96c9dfee42 fixed element-calling 2026-03-25 07:34:30 -05:00
naturallaw777 065b6c244b fixed element-calling 2026-03-25 07:23:16 -05:00
naturallaw777 e8a1c5fcd4 added livekit lk-jwt 2026-03-25 07:19:08 -05:00
naturallaw777 8e001eca91 updated element-calling 2026-03-25 07:07:59 -05:00
naturallaw777 6846a42c78 updated bip110 module 2026-03-24 23:06:28 -05:00
naturallaw777 6b4693f8b5 updated flake 2026-03-24 23:04:17 -05:00
naturallaw777 e4b2580683 updated bip110 module and flake 2026-03-24 23:02:02 -05:00
naturallaw777 ab14ad3f27 updated bip110 module 2026-03-24 22:51:39 -05:00
naturallaw777 17d4fcfd2c updated flake 2026-03-24 22:46:18 -05:00
naturallaw777 b5e3751a14 updated flake 2026-03-24 22:40:03 -05:00
naturallaw777 ec5e11ffbf updated flake 2026-03-24 22:31:51 -05:00
naturallaw777 157af12aed updated flake 2026-03-24 22:25:48 -05:00
naturallaw777 c941cf56f1 updated flake 2026-03-24 22:04:44 -05:00
naturallaw777 d0f2a018af updated flake 2026-03-24 22:00:37 -05:00
naturallaw777 c96de04b37 updated flake 2026-03-24 22:00:00 -05:00
naturallaw777 26ed8500f7 updated flake 2026-03-24 21:31:28 -05:00
naturallaw777 755f28146e updated flake 2026-03-24 21:29:30 -05:00
naturallaw777 8d123bd497 updated flake 2026-03-24 21:24:52 -05:00
naturallaw777 cf7a992663 updated flake 2026-03-24 21:21:00 -05:00
naturallaw777 0d8c607803 updated flake 2026-03-24 21:13:23 -05:00
naturallaw777 c897da00f7 updated bip110 2026-03-24 21:05:13 -05:00
naturallaw777 4654bd11d2 updated custom add on 2026-03-24 21:00:02 -05:00
naturallaw777 97ceca0599 rewored custom add on 2026-03-24 20:54:58 -05:00
naturallaw777 6ab52120e1 fixed rdp.nix and updated custom add readme 2026-03-24 20:53:01 -05:00
naturallaw777 7e4bcaa403 simplified RDP 2026-03-24 20:48:25 -05:00
naturallaw777 6e7e6a87ca rdp added systemd unit 2026-03-24 20:34:37 -05:00
naturallaw777 8bc1d6330a rdp added systemd unit 2026-03-24 20:24:05 -05:00
naturallaw777 2753047197 rdp added systemd unit 2026-03-24 20:15:41 -05:00
naturallaw777 f5e971a5b1 rdp retooling 2026-03-24 20:07:54 -05:00
naturallaw777 962fae0603 rdp retooling 2026-03-24 20:05:25 -05:00
naturallaw777 af704ac32c rdp retooling 2026-03-24 19:53:26 -05:00
naturallaw777 bde3b67b83 systemd rdp script fix 2026-03-24 19:34:35 -05:00
naturallaw777 45027d28da fixed systemd rdp.nix 2026-03-24 19:31:19 -05:00
naturallaw777 d907d67e7f fixed systemd rdp.nix 2026-03-24 19:26:59 -05:00
naturallaw777 96580b08b5 fixed handling rdp.nix 2026-03-24 19:18:11 -05:00
naturallaw777 017909323e fixed typo 2026-03-24 19:07:12 -05:00
naturallaw777 bbe96e9032 fixed typo 2026-03-24 19:05:54 -05:00
naturallaw777 0d9282b788 fixed bip110 2026-03-24 19:03:33 -05:00
naturallaw777 35189ab232 fixed element-calling 2026-03-24 18:57:32 -05:00
naturallaw777 f78f3e4740 fixed personalization 2026-03-24 18:50:22 -05:00
naturallaw777 0ca0bf9832 added script to make directories 2026-03-24 18:43:07 -05:00
naturallaw777 d5d5e747cb syntax error 2026-03-24 18:32:14 -05:00
naturallaw777 cc8fb206e6 syntax error 2026-03-24 18:28:46 -05:00
naturallaw777 88969a501c syntax error 2026-03-24 18:21:56 -05:00
naturallaw777 813c16166b retool personalization 2026-03-24 18:19:34 -05:00
naturallaw777 ea9b0c2a30 syntax error 2026-03-24 18:14:38 -05:00
naturallaw777 5a9acc5d83 fixed element-calling 2026-03-24 18:12:57 -05:00
naturallaw777 377c130508 fixed personalization.nix to help elment-calling 2026-03-24 18:10:24 -05:00
naturallaw777 83fb7769a8 fixed directory make 2026-03-24 17:53:41 -05:00
naturallaw777 4d7f560143 fixed directory make 2026-03-24 17:49:33 -05:00
naturallaw777 713ecfc282 fixed directory make 2026-03-24 17:44:34 -05:00
naturallaw777 647afc3980 fixed directory make 2026-03-24 17:37:49 -05:00
naturallaw777 030944624b fixed delayed loading for element-calling 2026-03-24 17:29:17 -05:00
naturallaw777 d3ddd85792 fixed syntax for elment-calling 2026-03-24 17:22:22 -05:00
naturallaw777 f02f87d6f6 fixed syntax 2026-03-24 17:17:30 -05:00
naturallaw777 a8b6a4d9e8 Rename roleLogic.nix to role-logic.nix 2026-03-24 17:15:33 -05:00
naturallaw777 ee7eb2238f added modules link 2026-03-24 17:10:37 -05:00
naturallaw777 3311ef51ee added modules 2026-03-24 17:06:37 -05:00
naturallaw777 b38b514606 fixed tooling 2026-03-24 17:03:02 -05:00
naturallaw777 5ccaecf12c fixed typo 2026-03-24 16:45:02 -05:00
naturallaw777 1565615cf7 fixed loop 2026-03-24 16:43:38 -05:00
naturallaw777 bae37a7059 retooling and updated README 2026-03-24 16:38:45 -05:00
naturallaw777 6202faa622 added BIP110 2026-03-24 10:49:26 -05:00
naturallaw777 32a8873f5e Nixpkgs Update 2026-03-23 11:50:40 -05:00
naturallaw777 1a35a96121 Nixpkgs Update 2026-03-06 11:48:51 -06:00
naturallaw777 e67fef017b updated outdated packages 2026-03-06 11:43:32 -06:00
naturallaw777 09ce6e8385 btcecosystem update 2026-03-02 16:37:14 -06:00
naturallaw777 6f5d396ddc syntax fix 2026-03-02 16:25:36 -06:00
naturallaw777 1212501b86 Nixpkgs Update and BTC Client Update 2026-03-02 15:27:55 -06:00
naturallaw777 c9a37a7bec updated custom-add on 2026-02-16 08:41:08 -06:00
naturallaw777 359795caf5 updated LICENSE Apache License 2026-02-16 08:36:00 -06:00
naturallaw777 2ea4b54aa5 removed duplicate file 2026-02-15 19:02:38 -06:00
naturallaw777 6e5701705b removed duplicate files 2026-02-15 19:01:04 -06:00
naturallaw777 a53dfd9271 merge sync 2026-02-15 18:50:03 -06:00
naturallaw777 efdec08eae added flapak repo 2025-11-12 15:26:39 -06:00
naturallaw777 d5d8de2ce3 Knots to NixOSstable 2025-10-28 15:05:52 -05:00
naturallaw777 d12024a166 Nixpkgs Update Bitcoin Knots 2025-10-28 15:04:14 -05:00
naturallaw777 83af2b52d8 updated Bitcoin excosystem1 2025-10-28 14:55:23 -05:00
naturallaw777 c7d595659a updated Bitcoin excosystem 2025-10-28 14:52:37 -05:00
naturallaw777 07543ca2bd removed out dated systemd-manager tooling 2025-10-28 14:31:44 -05:00
naturallaw777 00cd7b353b added axel 2025-10-24 15:32:04 -05:00
naturallaw777 d6d9de2f2e revised custom-add-on (2) 2025-09-19 22:13:29 -05:00
naturallaw777 43d861bd48 revised custom-add-on 2025-09-19 10:25:42 -05:00
naturallaw777 dd9f08a2c0 updated custom to reflect Knots 2025-09-19 10:22:50 -05:00
naturallaw777 3b3f30f4c9 updated custom to reflect Knots 2025-09-19 10:20:08 -05:00
naturallaw777 55cd2c4202 Nixpkgs update 2025-09-17 22:01:04 -05:00
naturallaw777 d514562280 Nixpkgs Update 2025-08-26 14:14:14 -05:00
naturallaw777 8895497abd update autologin 2025-08-02 21:57:43 -05:00
naturallaw777 959d75cf3a Nixpkgs Update and Bitcoin Client and Server Software Update 2025-08-02 21:52:35 -05:00
naturallaw777 971952cf6b Update Nixpkgs and Bitcoin client update 2025-07-16 13:00:24 -05:00
naturallaw777 3b5f0761e1 added btcserver-fix 2025-07-16 12:55:58 -05:00
naturallaw777 e39f0bdac8 added bigger buffer size 2025-07-08 14:30:31 -05:00
naturallaw777 ecfd3b63db removed jitsi 2025-06-22 21:16:31 -05:00
naturallaw777 9ffd2f180c Nixpkgsk Update and NixBitcoin Update 2025-06-22 19:41:49 -05:00
naturallaw777 acabc97cc0 removed nextcloud client and gnome extension 2025-06-22 19:14:27 -05:00
naturallaw777 7afe60951d updated configuration.nix again 2025-06-07 20:21:04 -05:00
naturallaw777 8d95d94d6b updated configuration.nix 2025-06-07 20:12:27 -05:00
naturallaw777 ed5461d167 Nixpkgs Update with Sparrow Update 2025-06-07 20:00:28 -05:00
naturallaw777 cffd470b10 Nixpkgs, Bitcoin Software Update 2025-05-27 18:15:53 -05:00
naturallaw777 02680c6acb Nixpkgs Update 2025-05-07 13:56:31 -05:00
naturallaw777 fa92b54661 Nixpkgs Update 2025-05-06 15:22:49 -05:00
naturallaw777 5b53d78614 Updated Nixpkgs 2025-04-24 23:53:12 -05:00
naturallaw777 a978cc1532 Nixpkgs Update 2025-04-20 20:24:09 -05:00
naturallaw777 10c156f9a9 updated cron script 2025-04-20 20:17:37 -05:00
naturallaw777 a0cb88d8fd updated configuration.nix 2025-04-20 20:07:58 -05:00
naturallaw777 b22412e5b6 added btc-clients flake 2025-04-20 20:02:10 -05:00
naturallaw777 1f6e0d2343 Nixpkgs Update 2025-04-09 09:58:27 -05:00
naturallaw777 34de3c0881 updated name of synadm 2025-03-24 10:58:19 -05:00
naturallaw777 76a06dd60a Update Nixpkgs 2025-03-23 19:45:46 -05:00
naturallaw777 fe5376c9a7 updated BTCPayserver, nixpkgs, and systemd-extension 2025-03-23 19:36:55 -05:00
naturallaw777 424743da78 Nixpkgs Update 2025-03-08 14:22:31 -06:00
naturallaw777 6df42d04ff increased php opcache 2025-02-26 22:35:53 -08:00
naturallaw777 f3a197ed7a Nixpkgs Update + Bisq1 Update 2025-02-24 10:31:41 -08:00
naturallaw777 63d1ce5a6c changed second drive script 2025-02-19 10:46:42 -08:00
naturallaw777 8c2477f46b updated second drive script 2025-02-19 10:29:58 -08:00
naturallaw777 6e961d2211 added wp admin 2025-02-16 19:17:37 -08:00
naturallaw777 d12f15a952 updated Sovran_Systems_File_Fixes 3 2025-02-13 06:53:39 -08:00
naturallaw777 21aceef8ec updated Sovran_Systems_File_Fixes 2025-02-13 06:51:34 -08:00
naturallaw777 5739b87c40 updated Sovran_Systems_File_Fixes 2025-02-13 06:49:36 -08:00
naturallaw777 6afdecd846 php 83 2025-02-13 06:38:45 -08:00
naturallaw777 011ed14424 php lowered 2025-02-13 06:32:47 -08:00
naturallaw777 bace5f0669 made php with own module 2025-02-13 01:40:37 -08:00
naturallaw777 da033d8d09 Nixpkgs Update 2025-02-12 10:16:34 -08:00
naturallaw777 b9cc954ea2 removed HBPS Nextcloud from modules 2025-02-10 15:27:42 -08:00
naturallaw777 bb4cc83c6d removed HBPS Nextcloud and old settings 2025-02-10 15:25:42 -08:00
naturallaw777 78fc6872b1 revert a1a09d5586
revert Update Nixpkgs
2025-02-10 15:14:43 -08:00
naturallaw777 a1a09d5586 Update Nixpkgs 2025-02-10 15:10:25 -08:00
naturallaw777 79df71a3e9 added jitsi-meet allow 2025-02-10 15:01:48 -08:00
naturallaw777 6b7401dd44 removed HBPS Nextcloud and old settings 2025-02-10 14:57:07 -08:00
naturallaw777 d752b9c3ce updated configuration.nix 2025-01-19 19:42:21 -08:00
naturallaw777 4ee2cda8f2 Update Nixpkgs and added BTCPayserver 2.0.5 2025-01-19 19:16:49 -08:00
naturallaw777 11bc2e39b3 updated README 2025-01-19 19:09:12 -08:00
naturallaw777 b121fc6549 updated nextcloud-push typo 2025-01-19 18:58:59 -08:00
naturallaw777 1efb0a2e31 updated nextcloud-push 2025-01-19 18:55:41 -08:00
naturallaw777 012a714cc3 updated flake.nix typo3 2025-01-19 18:47:37 -08:00
naturallaw777 7a88d57246 updated flake.nix typo2 2025-01-19 18:46:14 -08:00
naturallaw777 d123cb8f66 updated flake.nix udpdate 2025-01-19 18:45:25 -08:00
naturallaw777 2b763479cd updated flake.nix typo 2025-01-19 18:43:31 -08:00
naturallaw777 5153dabecb updated flake.nix with new inputs 2025-01-19 18:40:57 -08:00
naturallaw777 72f88b42a6 updated custom-add-ons.md 2024-12-23 08:42:54 -08:00
naturallaw777 eb36fdc01d Nixpkgs update and Nix-Bitcoin Update 2024-12-23 07:24:04 -08:00
naturallaw777 c77bed9e3a update php 2024-12-23 07:19:12 -08:00
naturallaw777 6d69fa3206 removed php worpress security 2024-12-22 23:27:24 -08:00
naturallaw777 6e19eee8bb added php worpress security 2024-12-22 23:21:08 -08:00
naturallaw777 35e5e6dfc5 updated php apcu 2024-12-20 12:24:43 -08:00
naturallaw777 f498f8d3b7 updated cron 2024-12-20 12:22:49 -08:00
naturallaw777 017ca5c8f9 resumed update-nix.sh 2024-12-17 14:48:21 -08:00
naturallaw777 8d4b5a3768 paused update-nix.sh 2024-12-17 12:21:50 -08:00
naturallaw777 1f4240bee4 updated update-nix.sh 2024-12-17 11:39:59 -08:00
naturallaw777 e0ec0c05ae updated update-nix.sh 2024-12-17 11:38:21 -08:00
naturallaw777 f27b4f6ba0 updated update-nix.sh 2024-12-17 11:36:01 -08:00
naturallaw777 4b0105f843 updated update-nix.sh 2024-12-17 11:32:54 -08:00
naturallaw777 916809ef51 updated update-nix.sh 2024-12-17 11:29:22 -08:00
naturallaw777 b3ed8ce489 update update-nix.sh 2024-12-17 11:25:29 -08:00
naturallaw777 1001ab07ec update update-nix.sh 2024-12-17 11:19:04 -08:00
naturallaw777 426a718615 code fix to update-agenix to systemd unit 2024-12-17 11:09:27 -08:00
naturallaw777 4f72f2f81c added to update-agenix to systemd unit 2024-12-17 11:01:09 -08:00
naturallaw777 f13e0509b9 added fix to update all agenix files 2024-12-17 10:55:32 -08:00
naturallaw777 9bb659341f updated inital script 2024-12-16 23:44:01 -08:00
naturallaw777 b62cb6b47b updated inital script 2024-12-16 23:42:24 -08:00
naturallaw777 5922b9a88c updated inital script 2024-12-16 23:32:00 -08:00
naturallaw777 7064b52c5c updated inital script 2024-12-16 23:24:12 -08:00
naturallaw777 da6f065097 updated permissions 2024-12-16 23:05:07 -08:00
naturallaw777 b7bf7016ef updated permissions 2024-12-16 21:54:22 -08:00
naturallaw777 d77f3e4873 updated synape personalizatoin configuration 2024-12-16 20:49:00 -08:00
naturallaw777 66cecf6a03 updated synape personalizatoin configuration 2024-12-16 20:41:09 -08:00
naturallaw777 4de15a4d08 updated synape personalizatoin configuration 2024-12-16 20:39:49 -08:00
naturallaw777 7481b51074 updated agenix system 2024-12-16 20:17:14 -08:00
naturallaw777 60eef20e1d updated agenix system 2024-12-16 19:29:32 -08:00
naturallaw777 a63adfaa0e updated agenix system 2024-12-16 19:15:06 -08:00
naturallaw777 8393821a8d updated agenix system 2024-12-16 19:12:06 -08:00
naturallaw777 e55a6e9f8e updated agenix system 2024-12-16 19:05:24 -08:00
naturallaw777 1ee7088733 updated typos 2024-12-16 18:32:54 -08:00
naturallaw777 3ae84cf77f updated auto database 2024-12-16 18:31:03 -08:00
naturallaw777 322f3c924c updated synapse.nix 2024-12-16 17:51:15 -08:00
naturallaw777 318fabd306 updated spacing on configuration.nix 2024-12-16 17:46:59 -08:00
naturallaw777 c833346149 updated the Agenix 2024-12-16 17:41:28 -08:00
naturallaw777 7dab002c4e updated the Agenix 2024-12-16 17:39:12 -08:00
naturallaw777 8380e90e2b updated the Agenix and Synapse 2024-12-16 17:35:15 -08:00
naturallaw777 424e75c3e4 updated the Agenix sub-systems 2024-12-16 17:31:07 -08:00
naturallaw777 b841623665 Update Nixpkgs 2024-12-16 13:11:03 -08:00
naturallaw777 027edaf93f updated custom-add-ons.md 2024-12-16 10:53:46 -08:00
naturallaw777 20ef4b5a09 removed outdated file 2024-12-16 10:43:41 -08:00
naturallaw777 203130594a Nixpkgs Update and CLN REST update 2024-12-13 12:52:44 -08:00
naturallaw777 588b2a7f7a Nixpkgs update plus New CLN REST connect 2024-12-13 12:52:13 -08:00
naturallaw777 fe75a76c43 revert b87c213326
revert updated cln connect
2024-12-10 14:26:45 -08:00
naturallaw777 b87c213326 updated cln connect 2024-12-10 14:22:34 -08:00
naturallaw777 f42663ae2c Nixpkgs Update and added Bisq2 2024-11-27 10:35:33 -08:00
naturallaw777 de3be3d52f Update Nixpkgs 2024-11-18 16:20:16 -08:00
naturallaw777 5812da1b15 updated PHP for Nextcloud 2024-11-07 10:21:52 -08:00
naturallaw777 a7d3e99638 Nixpkgs update with Gnome 47 2024-11-06 09:12:56 -08:00
naturallaw777 a0466b0f33 updated synapse.nix coturn 2024-11-04 01:01:23 -08:00
naturallaw777 249bf6fc1f Updated Coturn Settings fix typo 2024-11-04 00:58:53 -08:00
naturallaw777 e19b7d6c91 Updated Coturn Settings 2024-11-04 00:57:54 -08:00
naturallaw777 4ae735bbb3 added Nextcloud HPBS to Caddy 2024-11-04 00:49:24 -08:00
naturallaw777 82d284a08b added Nextcloud HPBS to modules.nix 2024-11-04 00:44:32 -08:00
naturallaw777 c32e3b5927 added Nextcloud HPBS to Configuration.nix 2024-11-04 00:07:05 -08:00
naturallaw777 97c26a40a5 added Nextcloud High Preformance Backend Server 2024-11-03 23:57:11 -08:00
naturallaw777 fa95e37c83 nixpkgs update 2024-10-24 06:24:12 -07:00
naturallaw777 5a545d22e2 nixpkgs update 2024-10-24 06:21:30 -07:00
naturallaw777 0a73ef93de nixpkgs update 2024-10-24 06:18:52 -07:00
naturallaw777 d0aa1735cb nixpkgs update 2024-10-24 06:17:34 -07:00
naturallaw777 19da88bd1a nixpkgs update 2024-10-24 00:14:09 -07:00
naturallaw777 e86ebe21cd nixpkgs update 2024-10-24 00:11:35 -07:00
naturallaw777 08a4f4d5dd nixpkgs update 2024-10-24 00:08:46 -07:00
naturallaw777 bc92b55636 nixpkgs update 2024-10-23 23:56:40 -07:00
naturallaw777 03854061bf nixpkgs update 2024-10-23 23:51:51 -07:00
naturallaw777 1526cc7ecb nixpkgs update 2024-10-23 23:43:59 -07:00
naturallaw777 5547f53232 nixpkgs update 2024-10-23 23:40:18 -07:00
naturallaw777 ca88934edd nixpkgs update 2024-10-23 23:35:42 -07:00
naturallaw777 a26421de88 nixpkgs update 2024-10-23 23:25:56 -07:00
naturallaw777 44706b0343 nixpkgs update 2024-10-23 23:22:28 -07:00
naturallaw777 e6d1660d15 Update Nixpkgs 2024-10-23 23:02:14 -07:00
naturallaw777 5ffd603261 added fix for bisq1 2024-10-23 22:48:04 -07:00
naturallaw777 2807aaaffa Nixpkgs Update with Sparrow Update 2024-10-08 09:33:43 -07:00
naturallaw777 177847a358 Update to Nixpkgs and Nix-Bitcoin 2024-09-16 12:09:03 -07:00
naturallaw777 7528286449 updated README and DIY 2024-09-14 10:14:39 -07:00
naturallaw777 b86f10cade updated README 2024-09-13 19:27:38 -07:00
naturallaw777 7a0c780e71 updated DIY Instructions 2024-09-13 19:25:26 -07:00
naturallaw777 710cf935e8 updated DIY Instructions 2024-09-13 19:23:34 -07:00
naturallaw777 40d794e2f9 updated DIY Instructions 2024-09-13 19:17:04 -07:00
naturallaw777 eb6915d2c0 updated DIY Instructions 2024-09-13 15:18:00 -07:00
naturallaw777 bd643e0513 updated sp.sh script 2024-09-12 15:01:47 -07:00
naturallaw777 5695f2e43d updated sp.sh script 2024-09-12 14:56:01 -07:00
naturallaw777 3e95d17b14 updated DIY install instructions 2024-09-12 14:36:34 -07:00
naturallaw777 01cd42ee2e updated DIY install instructions 2024-09-12 14:34:51 -07:00
naturallaw777 535bbbaa13 updated DIY install instructions 2024-09-12 14:24:29 -07:00
naturallaw777 011bbc1f23 updated DIY install instructions 2024-09-12 14:16:23 -07:00
naturallaw777 15f4aaab8f updated DIY install instructions 2024-09-12 14:12:20 -07:00
naturallaw777 ae157643ce updated DIY install instructions 2024-09-12 14:09:00 -07:00
naturallaw777 8668ab7231 updated DIY install instructions 2024-09-12 14:05:51 -07:00
naturallaw777 175ac4ad4e updated DIY install instructions 2024-09-12 13:51:38 -07:00
naturallaw777 1d320272e8 updated DIY install instructions 2024-09-12 13:50:19 -07:00
naturallaw777 29ca57ebb1 updated DIY install instructions 2024-09-12 13:48:39 -07:00
naturallaw777 f3f51da1e5 updated DIY install instructions 2024-09-12 13:44:32 -07:00
naturallaw777 e95e55e91b updated DIY install instructions 2024-09-12 13:36:32 -07:00
naturallaw777 10ad420d84 updated DIY install instructions 2024-09-12 13:34:39 -07:00
naturallaw777 f4ec53ab19 updated README 2024-09-12 13:15:09 -07:00
naturallaw777 8eb4cf5da0 updated README 2024-09-12 13:12:34 -07:00
naturallaw777 4684a70824 removed old DIY file 2024-09-12 12:58:28 -07:00
naturallaw777 a0301fe2f7 moved and renamed DIY instructions 2024-09-12 12:57:40 -07:00
naturallaw777 f15d828177 installer scripts 2024-09-12 12:52:16 -07:00
naturallaw777 8d783f34c9 Nixpkgs Update added Neovim 2024-09-05 08:06:38 -07:00
naturallaw777 b8a31f1f6d format clean up 2024-08-31 23:30:23 -07:00
naturallaw777 5fe30e3f4c added neovim 2024-08-31 20:28:23 -07:00
naturallaw777 e0342ef5c0 Nixpkgs update Nix-Bitcoin update Configuration.nix 2024-08-26 16:48:18 -07:00
naturallaw777 53abee0a58 Nixpkgs Update Plus Nix Bitcoin Update 2024-08-23 13:47:39 -07:00
naturallaw777 b428ab328f removed LibreWolf and added Jitsi-meet allow for now 2024-08-23 13:46:26 -07:00
naturallaw777 f635d661c1 added helix 2024-08-17 05:42:33 -07:00
naturallaw777 f0f7021699 added helix 2024-08-17 05:40:24 -07:00
naturallaw777 e960e9536f Udpated Sovran_Systems Helper 2024-08-09 15:58:54 -07:00
naturallaw777 2181386275 Udpated to newest Gnome 46 Smooth Screen 2024-08-09 15:48:29 -07:00
naturallaw777 848026b531 Nixpkgs update 2024-08-09 15:41:25 -07:00
naturallaw777 350243407c updated systemd-manager plugin 2024-08-07 21:22:28 -07:00
naturallaw777 3c932c998c added the gnome extension expresso 2024-08-06 16:25:45 -07:00
naturallaw777 c587322995 set default sleep 2024-08-06 16:06:43 -07:00
naturallaw777 632e9df288 updated PHP for Nextcloud 2024-08-06 16:00:18 -07:00
naturallaw777 c391351848 Nixpkgs Update with Nix-Bitcoin update 2024-08-03 15:31:43 -07:00
naturallaw777 9181942e4f fixed php issue 2024-08-03 15:17:24 -07:00
naturallaw777 3057093ce8 changed php memory limit 2024-08-03 13:59:55 -07:00
naturallaw777 a297668a73 updated php 2024-08-03 13:55:01 -07:00
naturallaw777 4d7547fad0 removed wordfence php 2024-08-03 13:39:12 -07:00
naturallaw777 f917a37fe3 updated php for wordpress 2024-08-03 13:09:05 -07:00
naturallaw777 a2ea28328c updated php for wordpress 2024-08-03 13:00:13 -07:00
naturallaw777 0770e52e4a updated php for wordpress 2024-08-03 12:51:47 -07:00
naturallaw777 8127a98cf7 updated php for wordpress 2024-08-03 12:48:38 -07:00
naturallaw777 2878087ccc updated php for wordpress 2024-08-03 12:40:21 -07:00
naturallaw777 427ff654ae updated php for wordpress 2024-08-03 12:16:43 -07:00
naturallaw777 14e7085ff9 updated php for wordpress 2024-08-03 12:07:53 -07:00
naturallaw777 e5b3f66df3 formated nixbitcoinecosystem.nix 2024-07-31 12:24:16 -07:00
naturallaw777 83885ac6b2 added clightning backup 2024-07-31 12:14:47 -07:00
naturallaw777 8034847f3d Updated Bitwarden package name 2024-07-19 22:40:29 -07:00
naturallaw777 40e9fb22d2 Removed "charge.lnd.service" 2024-07-19 22:38:19 -07:00
naturallaw777 3367a73a8a Update Nixpkgs, Removed Auto Power Reg, Added Gnome Performance Fix 2024-07-19 22:32:25 -07:00
naturallaw777 6eeb286fc4 Nixpkgs Update and RTL with Bolt12 2024-07-14 20:14:25 -07:00
naturallaw777 194dd7d85b Updated configuration.nix to reflect new nixpkgs 2024-07-08 09:54:53 -07:00
naturallaw777 4e3ac46769 Updated Nixpkgs and updated Nix-Bitcoin Ecosystem 2024-07-06 20:27:34 -07:00
naturallaw777 b04574d55b Added Bolt12 feature to CLN nodes 2024-07-03 12:55:32 -07:00
naturallaw777 1e82250705 Updated Caddy for Nextcloud "caldav" fix 2024-07-03 03:08:59 -07:00
naturallaw777 9c90bba795 Nixpkgs Update and Bisq 1.9.17 2024-07-02 09:04:41 -07:00
naturallaw777 152d1562a0 Nixpkgs update and configuration.nix update 2024-06-30 22:04:23 -07:00
naturallaw777 23cd38e2bc Updated Custom Readme 2024-06-28 20:01:43 -07:00
naturallaw777 5d69895ff4 Added CLN 2024-06-28 19:55:15 -07:00
naturallaw777 89a850a0af Udpate Nixpkgs and Bisq update 2024-06-28 18:37:01 -07:00
naturallaw777 431995502e removed ungoogled chromium 2024-06-15 10:06:44 -07:00
naturallaw777 69b2eb81c8 Nixpkgs Update 2024-06-15 09:24:39 -07:00
naturallaw777 044d0043be Updated Nixpkgs 2024-06-10 14:17:05 -07:00
naturallaw777 9f9658344f updated readme 2024-06-02 12:07:34 -07:00
naturallaw777 d72b46eae3 updated readme 2024-06-02 12:06:44 -07:00
naturallaw777 01b160b8ce updated readme 2024-06-02 12:06:09 -07:00
naturallaw777 33e0040260 updated readme 2024-06-02 12:05:21 -07:00
naturallaw777 460b14c452 updated readme 2024-06-02 12:03:55 -07:00
naturallaw777 c8da985b3e updated readme 2024-06-02 11:59:47 -07:00
naturallaw777 7e66bba9b4 updated readme 2024-06-02 11:59:11 -07:00
naturallaw777 055c760442 updated readme 2024-06-02 11:33:36 -07:00
naturallaw777 d2be6064e2 updated logo 2024-06-02 11:30:09 -07:00
naturallaw777 f42576dc20 Nixpkgs Update and Nix Bitcion Update 2024-05-27 12:17:16 -07:00
naturallaw777 9beb7188b5 added custom readme file 2024-05-27 12:04:19 -07:00
naturallaw777 8d9574ab32 Nixpkgs Update 2024-05-16 13:54:53 -07:00
naturallaw777 a38fac0d45 added dua and htop and removed ncdu_2 2024-05-16 13:25:30 -07:00
naturallaw777 34a430b1fc added new desktop settings and firefox settings 2024-05-12 16:50:39 -07:00
naturallaw777 560aaab551 removed chromium settings 2024-05-12 16:49:55 -07:00
naturallaw777 cd03cae16e added FireFox and added SCID alias to LND 2024-05-12 16:02:58 -07:00
naturallaw777 91dd12b528 Udpated OPcache.max.accelerated for PHP 2024-05-08 13:47:52 -07:00
naturallaw777 62f6469795 Nixpkgs Upddate with Gnome46 and Nix-Bitcoin update 2024-05-08 10:07:00 -07:00
naturallaw777 0442b09bdd udpated dconf file 2024-05-07 12:47:24 -07:00
naturallaw777 962531d1fc increased opcache.max_accelerated_files 2024-05-05 16:43:49 -07:00
naturallaw777 e9da09a637 updated config to support Nextcloud 29 2024-05-04 18:33:51 -07:00
naturallaw777 f13c4a4d40 Nixpkgs update with Nix Bitcoin update 2024-04-24 16:42:08 -07:00
naturallaw777 9072a8752f New Nixpkgs 2024-04-17 14:25:08 -07:00
naturallaw777 2be10e1bed Nixpkgs Update with Bisq 1.9.15 2024-04-16 09:32:11 -07:00
naturallaw777 9dc842f0ba Removed double LibreOffice 2024-04-10 16:42:43 -07:00
naturallaw777 e540080d64 Added Libre Office 2024-04-10 16:30:55 -07:00
naturallaw777 cf1d831e83 Nixpkgs Update 2024-04-09 09:29:09 -07:00
naturallaw777 6738119663 Nixpkgs Update with Nix Bitcoin Update 2024-04-06 10:41:09 -07:00
naturallaw777 c62c8fe6ce updated dconf file and added chromium.zip 2024-03-29 18:06:06 -07:00
naturallaw777 1d8cc52036 Update Nixpkgs and DIY install 2024-03-29 16:05:06 -07:00
naturallaw777 0336c6ce07 updated DIY install instructions 2024-03-29 13:26:09 -07:00
naturallaw777 e8ba5dd7d9 Nixpkgs update with the lastest version of Sparrow Wallet 2024-03-29 11:51:17 -07:00
naturallaw777 02e98adfbd Updated Nixpkgs 2024-03-25 14:14:27 -07:00
naturallaw777 5b635acd8e Added Sovran_SystemsOS_External_Backup app 2024-03-18 22:36:43 -07:00
naturallaw777 58aba363ae Nixpkgs Update 2024-03-18 13:36:12 -07:00
naturallaw777 1a722ccc79 Updated Systemd-manager to version 16 2024-03-16 17:33:58 -07:00
naturallaw777 0bf7b00fbc Nixpkgs Update 2024-03-15 18:18:20 -07:00
naturallaw777 c1a6db9bd6 removed external_backup app 2024-03-13 17:25:27 -07:00
naturallaw777 5943a4a5db NixPkgs Update and added Sovran_SystemsOS_External_Backup app 2024-03-13 16:00:56 -07:00
naturallaw777 e61ea88a27 Update Nixpkgs 2024-03-12 03:13:09 -07:00
naturallaw777 6f88e3dc62 Updated Nixpkgs 2024-03-07 22:26:56 -08:00
naturallaw777 6d3a4ffb83 Nix Bitcoin Update and Nixpkgs Update 2024-03-05 10:14:15 -08:00
naturallaw777 661d720d7c added sparrow back 2024-03-01 18:36:55 -08:00
naturallaw777 d93f9ce0cb removed attempted sparrow update 2024-03-01 18:32:34 -08:00
naturallaw777 d750c6fcc0 removed attempted sparrow update 2024-03-01 18:30:43 -08:00
naturallaw777 d9ea666c43 Revert "Updated Nixpkgs"
This reverts commit d1ffc8e8f3.
2024-03-01 18:27:28 -08:00
naturallaw777 501c6f6d35 updated sparrow wallet nix code again 2024-03-01 18:19:37 -08:00
naturallaw777 37333c55a5 updated sparrow wallet nix code 2024-03-01 18:17:59 -08:00
naturallaw777 234c609090 updated sparrow wallet 2024-03-01 18:14:13 -08:00
naturallaw777 d1ffc8e8f3 Updated Nixpkgs 2024-03-01 17:58:23 -08:00
naturallaw777 5aa1fc849c updated systemd-manager tree structure 2024-03-01 17:53:42 -08:00
naturallaw777 4228cc4190 updated systemd-manager 2024-03-01 17:45:55 -08:00
naturallaw777 f5179c8266 Updated Nixpkgs 2024-02-27 14:10:48 -08:00
naturallaw777 8744edc961 Nixpkgs Update 2024-02-22 21:02:53 -08:00
naturallaw777 dd5f708d71 updated bitcoin nix file 2024-02-19 09:44:46 -08:00
naturallaw777 35c91af826 updated again fix for nextcloud 28.0.2 2024-02-19 08:48:11 -08:00
naturallaw777 967aa4c03b updated fix for nextcloud 28.0.2 2024-02-19 08:43:01 -08:00
naturallaw777 16c0686c4c added fix for nextcloud 28.0.2 2024-02-19 08:23:31 -08:00
naturallaw777 ccb17b2e9b nixpkgs update 2024-02-16 18:15:00 -08:00
naturallaw777 ce9ac3f8cf Nixpkgs update 2024-02-12 18:26:38 -08:00
naturallaw777 23242f8ad3 Nixpkgs update plus Nix Bitcoin update 2024-02-12 16:41:51 -08:00
naturallaw777 f01ac8f4c4 added backup to 5 days instead of 7 2024-02-05 18:44:40 -08:00
naturallaw777 fb24df8c85 Nixpkgs Update 2024-02-04 08:48:27 -08:00
naturallaw777 426b1d1551 updated DIY install guide 2024-02-01 22:31:36 -08:00
naturallaw777 5a3d7aa3a8 Updated DIY Install 2024-02-01 22:26:35 -08:00
naturallaw777 e959936b8c updated DIY install guide 2024-02-01 22:23:07 -08:00
naturallaw777 9d20c4686f updated DIY install guide 2024-02-01 22:17:38 -08:00
naturallaw777 8c19fbacc4 removed chromium code 2024-02-01 21:26:07 -08:00
naturallaw777 58133631e3 updated readme 2024-02-01 21:21:17 -08:00
naturallaw777 78b9f7e0b2 nixpkgs update 2024-01-31 09:22:16 -08:00
naturallaw777 eab7ffd748 updated presonalization.nix 2024-01-29 13:18:45 -08:00
naturallaw777 d043d7112b removed 'www' and custom ACME from caddy and added secure boot feature 2024-01-29 13:14:16 -08:00
naturallaw777 a682abd501 updated cron script 2024-01-27 18:17:26 -08:00
naturallaw777 740f628c63 updated new sovran pro files 2024-01-27 17:06:50 -08:00
naturallaw777 74bf0c27a8 updated psp script 2024-01-26 21:24:19 -08:00
naturallaw777 943e970454 Nixpkgs Update; Added 16GB SWAP; Added systmed bitcoin fix 2024-01-26 21:15:31 -08:00
naturallaw777 4848dc5e42 update nixpkgs 2024-01-18 06:54:31 -08:00
naturallaw777 b7e31a07a0 updated sp script 2024-01-17 21:04:59 -08:00
naturallaw777 dc7ec80eec Added Mempool service 2024-01-17 13:58:45 -08:00
naturallaw777 2dde7bbd6e Update Nix Bitcoin and Nixpkgs 2024-01-16 12:24:02 -08:00
naturallaw777 cdd23cc942 updated all file permissions 2024-01-16 11:57:09 -08:00
naturallaw777 29cb2e2b6d updated install script 2024-01-15 22:49:41 -08:00
naturallaw777 6047e6c78e Nixpkgs Update 2024-01-15 13:44:38 -08:00
naturallaw777 129742949c updated matrix-synapse 2024-01-12 18:19:29 -08:00
naturallaw777 1a3a123395 Update to Nixpkgs 2024-01-10 09:33:15 -08:00
naturallaw777 e0b46f87ad removed outdated .nix file 2024-01-09 10:28:03 -08:00
naturallaw777 b6628b993e Updated Nixpkgs 2024-01-08 08:30:55 -08:00
naturallaw777 c38af88950 Updated Nixpkgs and increased max php upload and post size and reduced backup days 2024-01-04 20:34:16 -08:00
naturallaw777 74f43c07aa Updated Nixpkgs 2024-01-02 11:37:04 -08:00
naturallaw777 8aa4046273 updated the date 2024-01-01 15:02:04 -08:00
naturallaw777 4eff14d7b0 Nix Pkgs Update with an update to configuration.nix 2023-12-28 18:40:16 -08:00
naturallaw777 8d0d040379 Nixpkgs Update 2023-12-20 07:43:38 -08:00
naturallaw777 30d61fcbe5 Update Nixpkgs 2023-12-18 20:13:12 -08:00
naturallaw777 132a11ccc9 Updated NixOS update commands 2023-12-16 19:07:01 -08:00
naturallaw777 5cd1796531 Updated PHP for Nextcloud 28 2023-12-16 18:25:50 -08:00
naturallaw777 9fe75748bd New nix-bitcoin packages 2023-12-14 15:30:55 -08:00
naturallaw777 ef830a77bb Nixpkgs Update 2023-12-13 11:05:50 -08:00
naturallaw777 81f257c68c Added the temp allow electron app version so system will build 2023-12-11 15:38:10 -08:00
naturallaw777 1ca82d1648 Nixpkgs Update with Sparrow 1.8.1 2023-12-11 15:15:05 -08:00
naturallaw777 56851f32de Nixpkgs Update 2023-12-06 11:30:21 -08:00
naturallaw777 418933790a added ungoogled-chromium 2023-11-21 14:27:14 -08:00
naturallaw777 dc3c2bd64e updated bitcoin lnd node 2023-11-20 21:35:28 -08:00
naturallaw777 50cdf1d338 updated bitcoin lnd node 2023-11-20 21:34:12 -08:00
naturallaw777 ea252c9aaf updated bitcoin lnd node 2023-11-20 21:30:51 -08:00
naturallaw777 a1a734d6af Nixpkgs Update 2023-11-03 10:02:51 -07:00
naturallaw777 998202cdcc Nixpkgs Update 2023-11-01 20:15:54 -07:00
naturallaw777 6d16c11667 updated how to install Sovran_SystemsOS.md 2023-10-27 15:58:45 -07:00
naturallaw777 503231c060 Update Nixpkgs 2023-10-27 14:58:39 -07:00
naturallaw777 5ea9e1f42d updated how to install Sovran_SystemsOS.md 2023-10-27 14:17:04 -07:00
naturallaw777 c9142709c4 updated how to install Sovran_SystemsOS.md 2023-10-27 14:15:30 -07:00
naturallaw777 f466fb7683 updated how to install Sovran_SystemsOS.md 2023-10-27 14:14:04 -07:00
naturallaw777 8751636023 updated how to install Sovran_SystemsOS.md 2023-10-27 14:09:06 -07:00
naturallaw777 b548853041 updated how to install Sovran_SystemsOS.md 2023-10-27 14:07:34 -07:00
naturallaw777 ab6d6da2af updated how to install Sovran_SystemsOS.md 2023-10-27 14:06:29 -07:00
naturallaw777 58ae9352e8 updated how to install Sovran_SystemsOS.md 2023-10-27 13:33:36 -07:00
naturallaw777 920f6f4c8f updated how to install Sovran_SystemsOS.md 2023-10-27 13:32:28 -07:00
naturallaw777 0d29e3ab76 added how to install Sovran_SystemsOS.md 2023-10-27 13:07:41 -07:00
naturallaw777 ef2258f079 Updated Nixpkgs 2023-10-27 10:45:01 -07:00
naturallaw777 b4ec61d772 updated sp.sh 2023-10-27 07:04:14 -07:00
naturallaw777 eccec28441 added second drive installer script 2023-10-26 14:59:17 -07:00
naturallaw777 422a320505 Update to allow RTL to work in local network 2023-10-25 12:18:29 -07:00
naturallaw777 73b1b1772d New nixpkgs and nix-bitcoin 2023-10-25 11:24:56 -07:00
naturallaw777 e9f9f869eb removed nmap not needed 2023-09-23 21:04:09 -07:00
naturallaw777 e7b027dbd9 added nmap 2023-09-23 19:52:21 -07:00
naturallaw777 23b60a630c Nixpkgs update 2023-09-20 10:25:55 -07:00
naturallaw777 9265003bae Nixpkgs update 2023-09-16 18:20:04 -07:00
naturallaw777 90c2788823 added nextcloud-client 2023-09-11 10:22:16 -07:00
naturallaw777 3ad17dd1ba Nixpkgs Update 2023-09-10 16:00:04 -07:00
naturallaw777 5500785ea0 Nixpkgs Update 2023-09-09 09:58:14 -07:00
naturallaw777 27fb247e59 update readme.md 2023-09-08 10:04:13 -07:00
naturallaw777 f7a248e19a Nixpkgs Update 2023-09-06 21:42:12 -07:00
naturallaw777 8e4e54a981 update sp.sh 2023-09-06 12:57:19 -07:00
naturallaw777 4c587af34e update sp.sh 2023-09-05 22:42:29 -07:00
naturallaw777 e2fc57055b update sp.sh 2023-09-05 14:27:05 -07:00
naturallaw777 b658aef3c1 update sp.sh 2023-09-05 14:22:42 -07:00
naturallaw777 b6c1fd5eb1 update sp.sh 2023-09-05 11:41:16 -07:00
naturallaw777 b8c9717f52 update sp.sh and configuraiton.nix 2023-09-05 08:27:04 -07:00
naturallaw777 aa985b7be7 update configuration.nix 2023-09-04 23:13:28 -07:00
naturallaw777 ec28d1707c Nixpkgs Update 2023-09-04 22:25:31 -07:00
naturallaw777 b7e9e19181 New Nixpkgs 2023-08-29 20:48:48 -07:00
naturallaw777 e01d05c4f2 update external IP fetcher script 2023-08-25 20:58:45 -07:00
naturallaw777 1a1031a140 added dig 2023-08-25 20:32:52 -07:00
naturallaw777 a75da508f1 updated external ip fetcher script for sp.sh 2023-08-25 12:34:13 -07:00
naturallaw777 8f8e9f6152 updated external ip fetcher script for sp.sh 2023-08-25 12:31:39 -07:00
naturallaw777 dac82a48a1 updated personilzation.nix to better read local file 2023-08-22 13:56:28 -07:00
naturallaw777 0bde349068 updated sp.sh file to revert back to old nextcloud config as file locking is not working 2023-08-17 09:46:30 -07:00
naturallaw777 02335e5bde Nixpkgs Update 2023-08-16 11:54:58 -07:00
naturallaw777 8a32cd11d7 updated cron script and removed move onlyoffice code 2023-08-16 09:40:11 -07:00
naturallaw777 d7fc189480 Updated Main Flake and Update Nixpkgs 2023-08-11 11:28:14 -07:00
naturallaw777 3b3208a074 Nixpkgs Udpdate 2023-08-10 20:49:55 -07:00
naturallaw777 603692b901 updated config desktop 2023-08-10 20:20:29 -07:00
naturallaw777 e50a9386c8 updated custom.nix script 2023-08-10 18:50:04 -07:00
naturallaw777 0f4ea6af90 updated custom.nix script and added sovran-pro-flake-update2 2023-08-10 18:46:02 -07:00
naturallaw777 24f88c3e01 updated custom.nix script and added sovran-pro-flake-update2 2023-08-10 18:39:07 -07:00
naturallaw777 24cddc5f45 updated custom.nix script and added sovran-pro-flake-update2 2023-08-10 18:33:51 -07:00
naturallaw777 e90806961a updated custom.nix script and added sovran-pro-flake-update2 2023-08-10 18:30:33 -07:00
naturallaw777 7174185441 updated custom.nix script 2023-08-10 17:14:39 -07:00
naturallaw777 f94c079667 added custom.nix, libreoffice, nixbitcoin-Lockpackages,removed onlyoffice service 2023-08-10 17:09:16 -07:00
naturallaw777 f0237606bc added info regarding the key in file sp.sh 2023-08-03 12:53:21 -07:00
naturallaw777 1b9fa468e9 New Nixpkgs Update with erosnix update and nix-bitcoin update 2023-07-31 12:45:12 -07:00
naturallaw777 0438db4afc New Nixpkgs 2023-07-29 11:01:52 -07:00
naturallaw777 a80eb472ef removed test.sh 2023-07-29 09:43:26 -07:00
naturallaw777 2fcbd9c303 added script to upate /etc/nixos/flake.nix 2023-07-29 09:40:45 -07:00
naturallaw777 ca256bb1c6 final update to make git more efficient also flake will remain impure at this time 2023-07-29 07:24:03 -07:00
naturallaw777 76c37c93a3 udpated personalization.nix 2023-07-28 21:44:43 -07:00
naturallaw777 5f799cdf88 udpated personalization.nix 2023-07-28 21:43:12 -07:00
naturallaw777 9a0458addf udpated personalization.nix 2023-07-28 21:42:05 -07:00
naturallaw777 7d42c09114 udpated flake.nix 2023-07-28 21:40:26 -07:00
naturallaw777 860602f314 udpated flake.nix 2023-07-28 21:39:14 -07:00
naturallaw777 e660666b34 udpated flake.nix 2023-07-28 21:37:07 -07:00
naturallaw777 b743e445a3 updated personaliztion.nix 2023-07-28 20:03:43 -07:00
naturallaw777 b1d11d3aad updated personaliztion.nix 2023-07-28 19:48:15 -07:00
naturallaw777 741d8fdac0 udpated Sovran Pro flake.nix 2023-07-28 19:43:21 -07:00
naturallaw777 f7e05d9003 udpated main flake 2023-07-28 19:15:45 -07:00
naturallaw777 1518e37f0f udpate to make pure eval 2023-07-28 19:14:28 -07:00
naturallaw777 50d23a11c4 udpate to make added /etc/nixos/hardware-configuration.nix to modules 2023-07-28 19:12:01 -07:00
naturallaw777 b050750f82 udpate to make pure 2023-07-28 19:03:24 -07:00
naturallaw777 ea5e0d7898 udpate to make pure 2023-07-28 18:59:32 -07:00
naturallaw777 f089b05812 added personlization.nix to modules.nix 2023-07-28 17:44:35 -07:00
naturallaw777 c51cabac55 added personlization.nix to modules.nix 2023-07-28 17:40:40 -07:00
naturallaw777 13b6203a17 reagranged Sovran Pro flake 2023-07-28 17:37:19 -07:00
naturallaw777 7b4868b798 removed configuration.nix from the tree sturcture 2023-07-28 17:15:02 -07:00
naturallaw777 6c771f07c2 updated all files to make nixos rebuild pure 2023-07-28 17:12:53 -07:00
naturallaw777 f0e7ad2514 new nixpkgs and new dconf desktop file 2023-07-26 22:09:21 -07:00
naturallaw777 098684f234 Sovran_SystemOS update 2023-07-25 05:10:04 -07:00
naturallaw777 c3f05d6348 New System Wide Nixpkgs Update 2023-07-20 21:56:57 -07:00
naturallaw777 2ae9a51182 updated configuration.nix removed the permittedInsecurePackages 2023-07-20 21:55:57 -07:00
naturallaw777 c967e1d21e updated coturn.nix 2023-07-20 21:49:28 -07:00
naturallaw777 9acfc027a2 updated readme to include support link 2023-07-18 14:06:53 -07:00
naturallaw777 9931578960 added nodejs-slim-16.20.1 as nixbitcoin needs it 2023-07-18 12:01:47 -07:00
naturallaw777 1529f516e8 updated to nodejs-16.20.1 as nixbitcoin needs it 2023-07-18 12:00:07 -07:00
naturallaw777 2513be6d9e flake update 2023-07-18 11:56:49 -07:00
naturallaw777 9c2cb825e6 updated Sovran_SystemsOS look 2023-07-16 23:38:58 -07:00
naturallaw777 0d9b3ab1f0 added gnomeExtensions.date-menu-formatter 2023-07-16 20:10:44 -07:00
naturallaw777 48ee6e6164 updated sp.sh 2023-07-16 19:40:26 -07:00
naturallaw777 726dd66cbe added gnome terminal 2023-07-16 12:53:23 -07:00
naturallaw777 b55df0a2a7 updated sp.sh better able to run flatpak install 2023-07-16 12:47:54 -07:00
naturallaw777 792ac2ff1b updated sp.sh 2023-07-16 01:36:48 -07:00
naturallaw777 4de3d403f2 fixed spacing sp.sh 2023-07-16 01:23:12 -07:00
naturallaw777 a84176cf53 fixed scripting in sp.sh 2023-07-16 01:20:56 -07:00
naturallaw777 f501b33de8 updated congif for better external IP support 2023-07-16 00:16:37 -07:00
naturallaw777 3ea9614325 updated cron for external ip call 2023-07-16 00:06:13 -07:00
naturallaw777 a546a061af updated sp.sh 2023-07-15 19:34:35 -07:00
naturallaw777 a83cf2a711 updated sp.sh 2023-07-15 18:12:18 -07:00
naturallaw777 8904855421 updated psp.sh 2023-07-15 17:22:52 -07:00
naturallaw777 8cad569cef updated psp.sh 2023-07-15 14:06:32 -07:00
naturallaw777 9f72be2069 updated ps.sh and added wallpaper 2023-07-15 14:03:29 -07:00
naturallaw777 9eef6398bb updated ps.sh and added wallpaper 2023-07-15 11:52:47 -07:00
naturallaw777 5d081dd832 updated ps.sh and added wallpaper 2023-07-15 11:43:58 -07:00
122 changed files with 3275 additions and 20692 deletions
-11
View File
@@ -1,11 +0,0 @@
custom.nix
role-state.nix
*.iso
*.zip
*.pma
__pycache__/
*.pyc
*.pyo
iso/secrets/enroll-token
iso/secrets/provisioner-url
result
+251
View File
@@ -0,0 +1,251 @@
# Sovran Systems offers limited support of a DIY install of Sovran_SystemsOS. You can reach out to others in the matrix room https://matrix.to/#/#DIY_Sovran_SystemsOS:anarchyislove.xyz.
# These instructions will change over time due to new software development and Sovran Systems creator finding more efficient ways to install Sovran_SystemsOS. 9-12-2024
# Also, to fully complete the install, the Bitcoin blockchain will have to download. This could take up to 3 weeks.
# Lastly, if you gift to the computer movement <https://zaps.sovransystems.com> to receive a Sovran Pro, you do not have to do any of this. It is all done for you. On top of that, the Bitcoin blockchain is already installed. 😉
### Requirements
1. First computer with Linux OS already installed (like NixOS, Ubuntu, Arch, etc.) to download and burn the NixOS image to a USB thumb drive.
2. USB thumb drive 16GB or larger
3. Second computer that is ready to have Sovran_SystemsOS installed (Safe Boot turned off in the UEFI[BIOS] and be prepared for the entire storage drive to be ERASED!).
4. Second computer needs the following hardware specs:
- Intel or AMD processor (NO ARM processors)
- 32GB of RAM or Larger
- First main NVME internal drive to install Sovran_SystemsOS (500GB or larger)
- Second NVME internal drive to store the Bitcoin blockchain and the automatic backups (NVME 4TB or larger)
- Also, the second NVME internal drive needs to be installed FIRST into a USB enclosure. You will need a NVME USB enclosure. The USB enclosure will be plugged into the first Linux machine.
5. Working Internet connection for both computers
6. Personalized Domain names already purchased from Njal.la. See the explanation here: https://sovransystems.com/how-to-setup/
7. Your Router with ports open (Port Forwarding) to your second machine's internal IP address. This will usually be `192.168.1.(some number)` You will complete this at the end.
- Port 80
- Port 443
- Port 22
- Port 5349
- Port 8448
## Preparing the Second Internal Drive
1. Install the second NVME internal drive into the USB enclosure, NOT into the Second computer yet.
2. Plug in the USB enclosure into the first computer with Linux OS already installed into one of its available USB ports.
3. **Please Make Sure You Know The Existing Storage Names On This First Linux Computer. If You Run The Script Below And You Do Not Know What You Are Doing, You Could Potentially Erase Your First Linux Computer's Data. I Am Not Responsibly For Your Errors**
4. Open a terminal in the first Linux computer and log in as root.
5. Type in or copy and paste:
```bash
wget https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/sdpsp.sh
```
then press enter.
6. Now, type `bash sdpsp.sh` then press enter.
7. Then the screen will ask for "what block..." which will be the drive in the list that is not mounted, which will be the drive you just plugged in. It might be labeled `sda`, or `sdb` etc. Type in the drive name and press `enter`.
8. Then the screen will ask for "what partition...,"which will be whatever you typed into the first prompt, but with a "1" on it. For example, `sda1` or `sdb1`. Type it into the terminal and press `enter`.
9. Since the script is made to copy the blockchain from another Sovran Pro that already has the full blockchain installed it will throw an error. However, it should complete the setup just fine.
10. Once complete, remove the second drive from the USB enclosure and install it into your second computer in which you are installing Sovran_SystemsOS.
## Preparing the First Main Internal Drive
### Procedure One - Installing base NixOS
1. Still on the first computer with Linux OS already installed, download the latest NixOS <u>minimal</u> (64-bit Intel/AMD) image from here: https://nixos.org/download
2. Burn that ISO image onto the USB thumb drive.
3. Insert the newly created USB thumb drive with the ISO image into the second computer (the one you are installing Sovran_SystemsOS).
4. Reboot the second computer while the USB thumb drive is inserted and boot into the USB thumb drive. This may require you to press the F7 or F12 key at boot. (Also, make sure the second computer has "safe boot" turned off in the UEFI[BIOS]).
5. Proceed with the NixOS boot menu
6. Once at the command prompt type in `sudo su` to move to the root user
7. Once logged into the root user type in `passwd` then set the root user password to `a`
8. Type in `ip a` to get your internal IP address. It will usually be `192.1681.1.(somenumber)` make a note of this IP as you will need it later.
9. Now, that you are logged in as the root user type in or copy and paste:
```bash
curl https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/psp_physical_ram.sh -o psp_physical_ram.sh
```
the command to install the base NixOS and press enter.
10. Now, type `bash psp_physical_ram.sh` then press enter.
11. The script will ask for name of first main internal drive. It usually will be `nvme0n1`. Basically, it will be the drive without any data and it will not be mounted per the list on the screen. Type in the name and press enter on the keyboard.
12. Then the script will ask for the 'Boot' partition. It will be the SMALLER partition and usually named `nvme0n1p1`. Type in the name and press enter on the keyboard.
13. Then it will ask for the 'Primary' partition. It will be the LARGER partition usually named `nvme0n1p2`. Type in the name and press enter on the keyboard.
14. The script will finish installing the base NixOS. At the end it will ask for a root password. Type `a` and press enter and type `a` again to confirm and press enter.
15. The machine will reboot into a very basic install of NixOS command prompt.
16. Remove the USB thumb drive from the second computer.
### Procedure Two - Opening The Ports on Your Router - Internal IP
1. Go to port forwarding on your router and open the above mentioned ports to the internal IP (the one you found above) of your new Sovran_SystemsOS machine
### Procedure Three - Installing Sovran_SystemsOS
1. Now at the basic install of NixOS from Procedure One, type `root` to log into root and type the password `a` when asked then press enter.
2. Now you are logged in as `root`.
3. Now type in or copy and paste:
```bash
wget https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/sp.sh
```
then press enter.
4. Type in `bash sp.sh` then press enter.
5. Next the script will ask for your domain names from Njal.la. Type them in the corresponding prompts and then press enter for each prompt.
6. Then it will ask for an email for the SSL certificates. Type it in and press enter.
7. The script is long so it will take some time.
8. It will finish by stating `All Finished! Please Reboot then Enjoy your New Sovran Pro!`
9. Press the power button on the machine for it to turn off THEN press it again to power the machine
## Finishing the Install
### Putting the External IP of your New DIY Sovran Pro into your new domain names you just bought at [njal.la](https://njal.la)
1. On your New DIY Sovran Pro, log into your [njal.la](https://njal.la) account
2. Make a "dynamic" record for each subdomain
3. Njal.la will now display a `curl` command for each sub-domain.
4. Open the `Terminal` on your New DIY Sovran Pro and type in or copy and paste:
```bash
ssh root@localhost
```
It will as you for a password which is `gosovransystems` as this is the default temporary password from Sovran Systems.
Now you will be logged in as root.
5. Now type:
`nano /var/lib/njalla/njalla.sh`
and press enter.
3. Paste the `curl` commands from njal.la's website for each sub-domain. Each `curl` command gets a new line. For example:
```bash
...
curl "https://njal.la/update/?h=test.testsovransystems.com&k=8n7vk3afj-jkyg37&a=${IP}"
curl "https://njal.la/update/?h=zap.testsovransystems.com&k=8no*73afj-jkygi2ea=${IP}"
...
```
##### Make sure the default `&auto` from njal.la is replaced by `&a=${IP}` at the end of each `curl` command in the `/var/lib/njalla/njalla.sh` as in the example above.
7. After you have added all the sub-domins into `/var/lib/njalla/njalla.sh`, press `ctrl + s` then press `ctrl + x` to save and exit `nano`.
8. Close the `Terminal`.
### Setting the Desktop
1. Open the `Terminal` again and type in: `dconf load / < /home/free/Downloads/Sovran_SystemsOS-Desktop`. Do NOT log in as root.
2. Close the `Terminal`.
### Setting Up Nextcloud and Wordpress
#### Nextcloud
1. Open a web browser and navigate to your domain name you bought from [njal.la](https://njal.la) for example "cloud.myfreedomsite.com" you attributed to your Nextcloud instance.
2. Nextcloud will as you to set up a new account to be used as a log in. Do so.
3. Nextcloud will also ask you where you want the data directory. Type in `/var/lib/nextcloud/data`
4. Nextcloud will ask you to connect the database:
1. Choose `Postgresql` from the optoins.
2. Database username is `ncusr`
3. Database name is `nextclouddb`
4. Database password is found by doing this:
1. Open the `Terminal` again, then type in or copy and paste:
```bash
ssh root@localhost
```
Now you will be logged in as root.
2. Now type:
`cat /var/lib/secrets/nextclouddb`
and press enter.
3. Your database password will be displayed in the `Terminal` window.
4. Type that into the password field
5. Now, press `Install` on the Nextcloud website and Nextcloud will be installed. It will take a few minutes. Follow the on screen prompts.
#### Wordpress
1. Open a web browser and navigate to your domain name you bought from [njal.la](https://njal.la) for example "myfreedomsite.com" you attributed to your Wordpress instance.
2. Wordpress will ask you to connect the database:
1. Database username is `wpusr`
2. Database name is `wordpressdb`
4. Database password is found by doing this:
1. Open the `Terminal` again, then type in or copy and paste:
```bash
ssh root@localhost
```
Now you will be logged in as root.
2. Now type:
`cat /var/lib/secrets/wordpressdb`
and press enter.
3. Your database password will be displayed in the `Terminal` window.
4. Type that into the password field
5. Now, press `Install` on the Wordpress website and Wordpress will be installed. It will take a few minutes. Follow the on screen prompts.
### Final Install for Coturn, Flatpak, and Nextcloud
1. Staying in the `Terminal` type in or copy and paste:
```bash
sed -i '$e cat /var/lib/nextcloudaddition/nextcloudaddition' /var/lib/www/nextcloud/config/config.php
chown caddy:php /var/lib/www -R
chmod 700 /var/lib/www R
```
and press enter.
2. Now type or copy and paste:
```bash
set DOMAIN $(cat /var/lib/domains/matrix) && cp -n /var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/{$DOMAIN}/{$DOMAIN}.crt /var/lib/coturn/{$DOMAIN}.crt.pem && cp -n /var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/{$DOMAIN}/{$DOMAIN}.key /var/lib/coturn/{$DOMAIN}.key.pem && chown turnserver:turnserver /var/lib/coturn -R && chmod 770 /var/lib/coturn -R && systemctl restart coturn
```
and press enter.
3. Now type or copy and paste:
```bash
sudo -u free flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
```
and press enter.
It will ask for your `Administrator` password and to get the password open a new `Terminal` window and type:
```bash
ssh root@localhost
```
press enter.
Now you will be logged in as root.
Now type:
```bash
cat /var/lib/secrets/main
```
Then the `Administrator`'s password will be displayed. Copy and paste the password into the other `Terminal` window that is open. Then press enter.
Now you can close the `Terminal`.
### Everything now will be installed regarding Sovran_SystemsOS. The remaining setup will be only for the front-end user account creations for BTCpayserver, Vaultwarden, connecting the node to Sparrow wallet and Bisq.
### Congratulations! 🎉
+1
View File
@@ -659,3 +659,4 @@ specific requirements.
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
Regular → Executable
+180 -1
View File
@@ -1 +1,180 @@
### Testing Branch
# Sovran_SystemsOS
### The Officaly Repository of Sovran_SystemsOS and the Sovran Pro
**A declarative, self-hosted server and desktop operating system built on NixOS by [Sovran Systems](https://sovransystems.com)**
---
## Overview
Sovran_SystemsOS is a fully integrated NixOS configuration that transforms a single machine into a personal cloud, communications hub, Bitcoin node, web server, and **daily-use desktop** — all managed declaratively.
**It comes preinstalled on The Sovran Pro**
Every service is pre-wired: reverse proxy routing, database initialization, firewall rules, automated backups, and inter-service communication are handled out of the box. Moreover, you can activate the other custom packages; the system does the rest.
---
## Architecture
Sovran_SystemsOS is structured as a set of NixOS modules exposed via a flake. A remote machine consumes the flake and selectively enables features through a simple configuration interface.
```
Repository Main Flake (flake.nix)
└── Sovran_SystemsOS flake (nixosModules.Sovran_SystemsOS)
├── configuration.nix/ # Base system
│ ├── gnome Desktop # Gnome Desktop Interface
│ ├── caddy # Reverse proxy + HTTPS
│ ├── nextcloud # Cloud storage
│ ├── wordpress # CMS / publishing
│ ├── element # Matrix Synapse via Element Messaging App
├── modules/
│ ├── bitcoinecosystem.nix # Bitcoin Core / Knots / BTCPay Server / Bitcoin Lightning
│ ├── bip110.nix # Bip110 Node Consensus Policy
│ ├── element-calling.nix # Matrix Synapse via Element + Element Voice and Video Calling
│ ├── haven.nix # Nostr relay
│ ├── mempool.nix # Mempool explorer
│ ├── rdp.nix # Remote desktop (RDP)
│ ├── vaultwarden.nix # Password management
├── nix-bitcoin integration
├── bitcoin clients integration
│ ├── sparrow wallet # Trusted and Standard Open Source Bitcoin Wallet
│ ├── bisq/bisq2 # Non KYC Bitcoin Buying and Selling
├── agenix (secrets management)
└── nixvim
```
## Features
### Feature Toggles
[Custom Add-On Guide](custom-add-ons.md)
Every major service is gated behind a feature flag. Enable only what you need:
```nix
# custom.nix
{ config, pkgs, lib, ... }:
{
sovran_systemsOS = {
features = {
bip110 = lib.mkForce true;
element-calling = lib.mkForce true;
haven = lib.mkForce true;
mempool = lib.mkForce true;
rdp = lib.mkForce true;
};
nostr_npub = "pasteyournpubhere";
};
}
```
No unnecessary services run. No wasted resources.
---
### Service Stack
| Category | Service | Description |
|---|---|---|
| **Web** | Caddy | Automatic HTTPS, reverse proxy for all services |
| **Cloud** | Nextcloud | File storage, sync, and collaboration |
| **CMS** | WordPress | Self-hosted publishing and content management |
| **Passwords** | Vaultwarden | Bitwarden-compatible password vault |
| **Messaging** | Element/Matrix Synapse | Federated, decentralized messaging backend |
| **Video/Voice Calling** | Element Video and Voice Calling | Decentralized Voice Over IP for Matrix with optional TURN/STUN |
| **Bitcoin** | Bitcoin Core / Knots | **Full node with optional BIP-110 consensus policy** |
| **Bitcoin Lightning** | LND | Full LND Node Connected over Tor intergrated into BTCPay Server |
| **Payments** | BTCPay Server | Self-hosted Bitcoin payment processor |
| **Explorer** | Mempool | Bitcoin mempool visualizer and block explorer |
| **Nostr** | Haven | Nostr relay server |
| **Remote Access** | GNOME Remote Desktop | RDP access with auto-generated TLS and credentials |
---
### Security
- **SSH hardened** — password authentication disabled by default
- **Fail2ban** — active on https
- **Agenix** — encrypted secrets management integrated into the flake
- **Tor** — integration into the bitcoin ecosystem
- **Firewall** — ports managed per-module; only enabled services are exposed
### Reliability
- **Automated backups** via rsnapshot
- **Scheduled maintenance** via systemd timers
- **Database initialization** handled declaratively
- **Reproducible builds** — the main system is defined in code and can be rebuilt to match most systems
---
### Network Configuration
Sovran_SystemsOS hosts public-facing services (Wordpress, Element/Element Calling, Nextcloud, BTCPayserver, Haven Relay, and Vaultwarden) that require inbound connections from the internet. To make these services accessible outside your local network, you must configure **port forwarding** on your home router.
**Before deploying, ensure you have:**
- Access to your router's administration interface (typically at `192.168.1.1` or `192.168.0.1`)
- The ability to create port forwarding rules
- The local/private IP address of the machine running Sovran_SystemsOS
- The external public IP address of the machine running Sovran_SystemsOS
**Required port forwards (depending on enabled features):**
Forward each port to the **private IP address** of your Sovran_SystemsOS machine. Only forward ports for services you have enabled.
> **Tip:** Assign a static IP or DHCP reservation to your Sovran_SystemsOS machine so the forwarding rules remain valid after reboots.
> **Note:** If your ISP uses CGNAT (Carrier-Grade NAT), standard port forwarding will not work. Contact your ISP to request a public IP address.
---
## Installation
### Full Guide (Outdated as of now... will be working on a full .iso soon)
👉 [DIY Install Sovran_SystemsOS](https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/src/branch/main/DIY%20Install%20Sovran_SystemsOS.md)
---
## Requirements
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 4 cores | 8+ cores |
| RAM | 16 GB | 32+ GB |
| Storage | 512 GB SSD + 4 TB SSD | 2GB SSD + 4+ TB SSD (Bitcoin node requires significant disk) |
| Network | 100 Mbs Down/20 Mbs Up + No need for DDNS if domains are brought through https://njal.la | 1 Gbs Down/1 Gbs Up + No need for DDNS if domains are brought through https://njal.la |
---
## Community
| Channel | Link |
|---|---|
| General Chat | [#sovran-systems:anarchyislove.xyz](https://matrix.to/#/#sovran-systems:anarchyislove.xyz) |
| DIY Support | [#DIY_Sovran_SystemsOS:anarchyislove.xyz](https://matrix.to/#/#DIY_Sovran_SystemsOS:anarchyislove.xyz) |
---
## License
[GNU Affero General Public License v3.0](LICENSE)
---
## Project Philosophy
Sovran_SystemsOS exists to provide a complete, self-hosted infrastructure stack that eliminates dependency on third-party platforms. It is opinionated by design — services are pre-integrated so you spend time using your system, not assembling it.
You retain full visibility into every module, every service definition, and every configuration choice. Nothing is hidden. Everything is reproducible.
---
**Be Digitally Sovereign**
-244
View File
@@ -1,244 +0,0 @@
<svg width="256" height="256" version="1.1" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="256" height="256" image-rendering="optimizeQuality" preserveAspectRatio="none" xlink:href="data:image/png;base64,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
"/>
</svg>

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.3 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="1024" height="1024" viewBox="-34 -34 580 580"><filter id="b"><feColorMatrix in="SourceAlpha" result="matrixOut" type="saturate" values=".1"/><feGaussianBlur in="matrixOut" result="blur-out" stdDeviation="6"/><feColorMatrix in="blur-out" result="color-out" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.35 0"/><feBlend in="SourceGraphic" in2="color-out"/></filter><circle cx="255" cy="255" r="200" fill="#fff"/><radialGradient id="a" cx="277.49" cy="196.441" r="34.397" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#f9aa4b"/><stop offset="1" style="stop-color:#f7931a"/></radialGradient><path fill="url(#a)" d="m254.647 174.6-13.983 56.08c15.855 3.951 64.735 20.071 72.656-11.656 8.248-33.096-42.817-40.472-58.673-44.424" filter="url(#b)"/><radialGradient id="c" cx="261.915" cy="284.567" r="39.838" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#f9aa4b"/><stop offset="1" style="stop-color:#f7931a"/></radialGradient><path fill="url(#c)" d="m233.608 258.984-15.425 61.832c19.04 4.729 77.769 23.584 86.448-11.296 9.072-36.376-51.984-45.784-71.023-50.536" filter="url(#b)"/><radialGradient id="d" cx="256.028" cy="256.003" r="255.988" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#f9aa4b"/><stop offset="1" style="stop-color:#f7931a"/></radialGradient><path fill="url(#d)" d="M317.871 7.656c-137.12-34.192-276.024 49.28-310.2 186.44-34.208 137.136 49.256 276.048 186.36 310.24 137.16 34.199 276.063-49.265 310.256-186.408 34.192-137.152-49.264-276.08-186.416-310.272m50.936 211.872c-3.688 24.936-17.512 37.008-35.864 41.24 25.2 13.12 38.024 33.239 25.809 68.12-15.16 43.319-51.176 46.976-99.072 37.912l-11.624 46.584-28.088-7 11.472-45.96a1076 1076 0 0 1-22.384-5.809l-11.512 46.177-28.056-7 11.624-46.673c-6.561-1.68-13.225-3.464-20.024-5.168l-36.552-9.111 13.943-32.152s20.696 5.504 20.416 5.096c7.952 1.969 11.48-3.216 12.872-6.672l18.368-73.64.048-.2 13.104-52.568c.344-5.968-1.712-13.496-13.088-16.336.439-.296-20.4-5.072-20.4-5.072l7.472-30 38.736 9.673-.032.144c5.824 1.448 11.824 2.824 17.937 4.216L245.423 89.2l28.072 7-11.28 45.224c7.536 1.721 15.12 3.456 22.504 5.297l11.2-44.929 28.088 7-11.504 46.145c35.464 12.215 61.401 30.527 56.304 64.591" filter="url(#b)"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

-177
View File
@@ -1,177 +0,0 @@
<svg width="512" height="512" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="512" height="512" image-rendering="optimizeQuality" preserveAspectRatio="none" xlink:href="data:image/png;base64,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
"/>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

-63
View File
@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
width="1024"
height="1024"
version="1.1"
id="svg7"
sodipodi:docname="BTCPay Server Icon_Version2 (1) (1).svg"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.360203"
inkscape:cx="512.21117"
inkscape:cy="512.21117"
inkscape:window-width="2346"
inkscape:window-height="1355"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="svg7" />
<defs
id="defs1">
</defs>
<path
d="M 395.38615,1007.3428 A 57.385688,57.385688 0 0 1 337.99353,949.95705 V 86.24698 a 57.39955,57.39955 0 0 1 114.78524,0 v 863.71007 a 57.385688,57.385688 0 0 1 -57.39262,57.38575"
style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.93064"
id="path2" /><path
d="M 395.42245,1007.3558 A 57.39955,57.39955 0 0 1 370.7771,898.10115 L 716.85161,733.85888 361.3445,471.95004 a 57.385688,57.385688 0 0 1 -12.15634,-80.2568 57.385688,57.385688 0 0 1 80.236,-12.15634 l 432.75601,318.83017 a 57.371827,57.371827 0 0 1 -9.43953,98.04775 L 419.99156,1001.7974 a 57.177769,57.177769 0 0 1 -24.56911,5.5584"
style="fill:#51b13e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.93064"
id="path3" /><path
d="M 395.43261,667.85437 A 57.385688,57.385688 0 0 1 361.34773,564.25518 L 716.84791,302.33942 370.78033,138.09021 A 57.385688,57.385688 0 0 1 343.53599,61.638322 c 13.59791,-28.6374 47.83527,-40.842254 76.45187,-27.237411 L 852.74387,239.77654 a 57.385688,57.385688 0 0 1 9.4326,98.05468 l -432.74908,318.8371 a 57.177769,57.177769 0 0 1 -33.99478,11.18605"
style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.93064"
id="path4" /><path
d="M 452.77605,396.7388 V 639.46363 L 617.42723,518.15666 Z"
style="fill:#1e7a44;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.93064"
id="path5" /><path
d="M 452.77896,574.75277 H 337.99334 V 294.96882 h 114.78562 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.93065"
id="path6" /><path
d="M 395.38615,28.855342 A 57.385688,57.385688 0 0 0 337.99353,86.24796 V 808.52449 H 452.77877 V 86.24796 A 57.385688,57.385688 0 0 0 395.38615,28.855342"
style="fill:#cedc21;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.93064"
id="path7" />
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

-93
View File
@@ -1,93 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="96.767509"
height="92.983162"
viewBox="0 0 96.767509 92.983162"
version="1.1"
xml:space="preserve"
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linecap:round;stroke-linejoin:round"
id="svg19"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs19" />
<g
transform="translate(-51.987986,-84.712078)"
id="g19">
<g
id="Icon"
transform="matrix(0.99999699,0,0,1.0000031,-3876.6653,-1644.4859)">
<g
id="g3">
<g
transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)"
id="g1">
<path
d="m 3901.56,610.734 c -8.03,-0.473 -15.5,-2.634 -22.36,-5.857 -6.96,-3.269 -13.16,-7.784 -18.4,-13.244 -2.09,-2.176 -4.04,-4.484 -5.83,-6.924 -1.77,-2.428 -3.4,-4.976 -4.84,-7.643 -4.24,-7.842 -6.92,-16.685 -7.24,-26.198 -0.32,-7.547 0.75,-14.813 3.05,-21.561 2.43,-7.104 6.14,-13.611 10.89,-19.258 l -1.04,-0.954 c -5.4,5.445 -9.77,11.886 -12.89,19.03 -3.06,7 -4.87,14.656 -5.22,22.743 -0.34,10.523 1.83,20.557 6.11,29.438 1.48,3.074 3.24,5.998 5.22,8.743 2,2.757 4.23,5.341 6.68,7.693 6.06,5.826 13.31,10.448 21.34,13.358 7.63,2.767 15.93,3.959 24.53,3.452 z"
style="fill:#1f88c0"
id="path1" />
</g>
<g
transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)"
id="g2">
<path
d="m 3875.69,496.573 c 3.93,-2.035 8.11,-3.676 12.51,-4.787 4.29,-1.082 8.76,-1.662 13.36,-1.754 2.26,0.098 4.47,0.3 6.65,0.656 8.92,1.459 16.98,5.126 24.1,9.995 3.82,2.611 7.28,5.652 10.5,8.936 4.28,4.361 8.08,9.197 11.04,14.613 4.35,7.965 7.11,16.954 7.47,26.636 0.29,7.88 -0.86,15.477 -3.44,22.454 -1.79,4.847 -4.18,9.431 -7.22,13.516 -3.44,4.623 -7.7,8.589 -12.39,11.931 -4.61,3.286 -9.74,5.85 -15.18,7.709 -0.72,0.243 -1.49,0.327 -2.16,0.689 -0.51,0.281 -0.79,0.687 -1.24,1.057 l 0.68,2.165 c 0.61,0.043 1.1,0.184 1.7,0.085 0.79,-0.13 1.48,-0.591 2.21,-0.908 7.71,-3.35 14.54,-8.211 20.29,-14.138 2.45,-2.525 4.68,-5.254 6.74,-8.109 2.28,-3.151 4.35,-6.466 6.12,-9.971 4.04,-8.008 6.58,-16.926 6.93,-26.48 0.38,-10.357 -1.7,-20.24 -5.88,-29 -2.91,-6.093 -6.76,-11.705 -11.53,-16.39 -3.58,-3.516 -7.69,-6.488 -12.11,-8.916 -7.96,-4.37 -16.97,-6.802 -26.47,-7.333 -2.25,-0.125 -4.51,-0.175 -6.81,-0.075 -4.69,-0.094 -9.26,0.365 -13.67,1.243 -4.59,0.912 -9,2.286 -13.18,4.128 z"
style="fill:#1f88c0"
id="path2" />
</g>
</g>
<g
id="g6">
<g
transform="matrix(-3.37109,-0.514565,0.514565,-3.37109,4078.07,1806.88)"
id="g4">
<path
d="m 22,12 c 0,-1.097 -0.903,-2 -2,-2 -0.579,0 -1.103,0.251 -1.47,0.649 C 18.202,11.006 18,11.481 18,12 c 0,1.097 0.903,2 2,2 1.097,0 2,-0.903 2,-2 z"
style="fill:none;fill-rule:nonzero;stroke:#1f88c0;stroke-width:1.05px"
id="path3" />
</g>
<g
transform="matrix(-5.33921,-5.26159,-3.12106,-6.96393,4073.87,1861.55)"
id="g5">
<path
d="m 10.315,5.333 c 0,0 -0.567,0.588 -1.285,1.34 C 7.768,7.995 6.054,9.805 6.054,9.805 L 6.237,9.86 c 0,0 1.808,-1.783 3.123,-3.089 0.747,-0.743 1.329,-1.327 1.329,-1.327 z"
style="fill:#1f88c0"
id="path4" />
</g>
</g>
<g
id="Padlock"
transform="matrix(3.11426,0,0,3.11426,3938.31,1737.25)">
<g
id="g7">
<path
d="m 9.876,21 h 8.286 C 18.625,21 19,20.625 19,20.162 V 11.838 C 19,11.375 18.625,11 18.162,11 H 5.838 C 5.375,11 5,11.375 5,11.838 v 4.92"
style="fill:none;stroke:#22b638;stroke-width:1.89px;stroke-linecap:butt;stroke-linejoin:miter"
id="path6" />
<path
d="M 8,11 V 7 c 0,-2.194 1.806,-4 4,-4 2.194,0 4,1.806 4,4 v 4"
style="fill:none;fill-rule:nonzero;stroke:#22b638;stroke-width:1.89px"
id="path7" />
</g>
</g>
<g
id="g10">
<g
transform="matrix(5.30977,0.697415,-0.697415,5.30977,3852.72,1727.97)"
id="g8">
<path
d="M 22,12 C 22,11.659 21.913,11.337 21.76,11.055 21.421,10.429 20.756,10 20,10 c -1.097,0 -2,0.903 -2,2 0,1.097 0.903,2 2,2 1.097,0 2,-0.903 2,-2 z"
style="fill:none;fill-rule:nonzero;stroke:#1f88c0;stroke-width:0.98px"
id="path8" />
</g>
<g
transform="matrix(4.93114,2.49604,1.11018,5.44847,3921.41,1726.72)"
id="g9">
<path
d="m 8.902,6.77 c 0,0 -1.667,1.483 -2.875,2.596 -0.684,0.63 -1.208,1.136 -1.208,1.136 l 0.701,0.662 c 0,0 0.501,-0.527 1.126,-1.213 C 7.749,8.739 9.219,7.068 9.219,7.068 Z"
style="fill:#1f88c0"
id="path9" />
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

-72
View File
@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 85.3 85.3" style="enable-background:new 0 0 85.3 85.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:#083766;}
.st1{fill:#FFFFFF;}
.st2{fill:none;stroke:#1250A9;stroke-width:1.9869;stroke-miterlimit:10;}
.st3{fill:none;stroke:#1859D1;stroke-width:1.9869;stroke-miterlimit:10;}
.st4{fill:none;stroke:#1853FF;stroke-width:1.9869;stroke-miterlimit:10;}
</style>
<g>
<path class="st0" d="M84.7,41.6L84.7,41.6l-3.6-2.2c0-0.4-0.1-0.7-0.1-1l3.1-2.9c0.3-0.3,0.4-0.7,0.4-1.1c-0.1-0.4-0.4-0.8-0.8-0.9
L79.8,32c-0.1-0.3-0.2-0.7-0.3-1l2.5-3.4c0.3-0.3,0.3-0.8,0.1-1.2c-0.2-0.4-0.5-0.7-0.9-0.8L77,24.9c-0.2-0.3-0.3-0.6-0.5-0.9
l1.8-3.8c0.2-0.4,0.1-0.8-0.1-1.2c-0.2-0.4-0.6-0.6-1.1-0.5l-4.2,0.1c-0.2-0.3-0.4-0.5-0.7-0.8l1-4.1c0.1-0.4,0-0.9-0.3-1.2
c-0.3-0.3-0.7-0.4-1.2-0.3l-4.1,1c-0.3-0.2-0.5-0.4-0.8-0.7l0.2-4.2c0-0.4-0.2-0.8-0.6-1.1C66,7,65.5,6.9,65.2,7.1l-3.8,1.7
c-0.3-0.2-0.6-0.3-0.9-0.5l-0.7-4.2c-0.1-0.4-0.4-0.8-0.8-0.9c-0.4-0.2-0.9-0.1-1.2,0.1l-3.4,2.5c-0.3-0.1-0.7-0.2-1-0.3l-1.5-3.9
c-0.1-0.4-0.5-0.7-0.9-0.8c-0.4-0.1-0.9,0.1-1.1,0.4L47,4.3c-0.3,0-0.7-0.1-1-0.1l-2.2-3.6C43.5,0.2,43.1,0,42.7,0
c-0.4,0-0.8,0.2-1.1,0.6l-2.2,3.6c-0.3,0-0.7,0.1-1,0.1l-2.9-3.1c-0.3-0.3-0.7-0.4-1.1-0.4c-0.4,0.1-0.8,0.4-0.9,0.8L32,5.5
c-0.3,0.1-0.7,0.2-1,0.3l-3.4-2.5c-0.3-0.3-0.8-0.3-1.2-0.1c-0.4,0.2-0.7,0.5-0.7,0.9l-0.7,4.2c-0.3,0.2-0.6,0.3-0.9,0.5l-3.8-1.7
C19.8,6.9,19.3,7,19,7.2c-0.4,0.2-0.6,0.6-0.6,1.1l0.1,4.2c-0.3,0.2-0.5,0.4-0.8,0.7l-4.1-1c-0.4-0.1-0.9,0-1.2,0.3
c-0.3,0.3-0.4,0.7-0.3,1.2l1,4.1c-0.2,0.3-0.4,0.5-0.7,0.8l-4.2-0.1c-0.4,0-0.8,0.2-1.1,0.5c-0.2,0.4-0.3,0.8-0.1,1.2L8.9,24
c-0.2,0.3-0.3,0.6-0.5,0.9l-4.1,0.7c-0.4,0.1-0.8,0.4-0.9,0.8c-0.2,0.4-0.1,0.8,0.1,1.2l2.5,3.4c-0.1,0.3-0.2,0.7-0.3,1l-3.9,1.5
c-0.4,0.1-0.7,0.5-0.8,0.9c-0.1,0.4,0.1,0.9,0.4,1.1l3.1,2.9c0,0.3-0.1,0.7-0.1,1l-3.6,2.2h0C0.2,41.8,0,42.2,0,42.7
c0,0.4,0.2,0.8,0.6,1l3.6,2.2c0,0.3,0.1,0.7,0.1,1l-3.1,2.9c-0.3,0.3-0.5,0.7-0.4,1.1c0.1,0.4,0.4,0.8,0.8,0.9l3.9,1.5
c0.1,0.3,0.2,0.7,0.3,1l-2.5,3.4c-0.2,0.3-0.3,0.8-0.1,1.2c0.1,0.4,0.5,0.7,0.9,0.7l4.1,0.7c0.2,0.3,0.3,0.6,0.5,0.9l-1.7,3.8
c-0.2,0.4-0.1,0.8,0.1,1.2c0.2,0.4,0.6,0.6,1.1,0.5l4.2-0.1c0.2,0.3,0.4,0.5,0.7,0.8l-1,4.1c-0.1,0.4,0,0.8,0.3,1.1
c0.3,0.3,0.7,0.4,1.2,0.3l4.1-1c0.3,0.2,0.5,0.4,0.8,0.7L18.4,77c0,0.4,0.2,0.8,0.6,1.1c0.3,0.2,0.8,0.3,1.2,0.1l3.8-1.7
c0.3,0.2,0.6,0.3,0.9,0.5l0.7,4.1c0.1,0.4,0.4,0.8,0.7,0.9c0.4,0.2,0.8,0.1,1.2-0.1l3.4-2.5c0.3,0.1,0.7,0.2,1,0.3l1.5,3.9
c0.1,0.4,0.5,0.7,0.9,0.8c0.4,0.1,0.9-0.1,1.1-0.4l2.9-3.1c0.4,0,0.7,0.1,1,0.1l2.2,3.6c0.2,0.4,0.6,0.6,1.1,0.6
c0.4,0,0.8-0.2,1.1-0.6l2.2-3.6c0.3,0,0.7-0.1,1-0.1l2.9,3.1c0.3,0.3,0.7,0.5,1.1,0.4c0.4-0.1,0.8-0.4,0.9-0.8l1.5-3.9
c0.3-0.1,0.7-0.2,1-0.3l3.4,2.5c0.4,0.2,0.8,0.3,1.2,0.1c0.4-0.2,0.7-0.5,0.7-0.9l0.7-4.1c0.3-0.2,0.6-0.3,0.9-0.5l3.8,1.7
c0.4,0.2,0.8,0.1,1.2-0.1c0.4-0.2,0.6-0.6,0.5-1.1l-0.2-4.2c0.3-0.2,0.5-0.4,0.8-0.7l4.1,1c0.4,0.1,0.9,0,1.2-0.3
c0.3-0.3,0.4-0.7,0.3-1.1l-1-4.1c0.2-0.3,0.4-0.5,0.7-0.8l4.2,0.1c0.4,0,0.8-0.2,1.1-0.5c0.2-0.4,0.3-0.8,0.1-1.2l-1.7-3.8
c0.2-0.3,0.3-0.6,0.5-0.9l4.1-0.7c0.4-0.1,0.8-0.3,0.9-0.7c0.2-0.4,0.1-0.9-0.1-1.2l-2.4-3.4c0.1-0.3,0.2-0.7,0.3-1l3.9-1.5
c0.4-0.2,0.7-0.5,0.8-0.9c0.1-0.4-0.1-0.8-0.4-1.1L81.1,47c0-0.3,0.1-0.7,0.1-1l3.6-2.2c0.4-0.2,0.6-0.6,0.6-1
C85.3,42.2,85.1,41.8,84.7,41.6z M62.4,66.6c-1.1,0.9-2.4,1.8-3.6,2.6c-4.7,2.9-10.3,4.5-16.3,4.5c-3.4,0-6.7-0.5-9.8-1.6
c-0.1,0-0.2-0.1-0.4-0.1c-1.1-0.4-2.1-0.8-3.2-1.2v0C28.1,70.3,27,69.7,26,69c-1.2-0.8-2.4-1.6-3.5-2.5c-0.9-0.7-1.7-1.5-2.5-2.3
l-0.1,0l-0.1,0c-1-1.1-2-2.2-2.9-3.4c0,0,0.1,0,0.1,0c-3.4-4.6-5.5-10.1-6-16.1h0c0-0.2,0-0.3,0-0.5c0-0.2,0-0.3,0-0.4
c0-0.1,0-0.3,0-0.4c0-0.3,0-0.5,0-0.8v-0.1l0,0c0-0.8,0-1.6,0.1-2.4c0-0.2,0-0.4,0.1-0.7h0l0.2-0.1c0.1-1.2,0.3-2.4,0.6-3.5
c0.4-1.6,0.8-3.2,1.4-4.8c0.5-1.3,1.1-2.6,1.8-3.9L15,27c2.3-4,5.5-7.5,9.2-10.1c0.1-0.1,0.3-0.2,0.4-0.3c4.4-3.1,9.6-5,15.3-5.4
c0.8-0.1,1.7-0.1,2.5-0.1c0.8,0,1.5,0,2.2,0.1c12.3,0.9,22.6,8.8,26.9,19.8c0.6,1.6,1.1,3.2,1.5,4.8c0.5,2.2,0.7,4.4,0.7,6.7
C73.9,52.1,69.4,60.9,62.4,66.6z"/>
<circle class="st1" cx="42.7" cy="42.7" r="33.5"/>
<g>
<ellipse class="st2" cx="42.7" cy="42.8" rx="27.2" ry="13.6"/>
<ellipse transform="matrix(0.8957 -0.4447 0.4447 0.8957 -14.4551 23.4064)" class="st3" cx="42.7" cy="42.5" rx="13.6" ry="27.2"/>
<path class="st4" d="M30.6,67.1c6.7,3.3,17.6-4.9,24.2-18.3s6.6-27-0.1-30.4"/>
<ellipse transform="matrix(0.4447 -0.8957 0.8957 0.4447 -14.6426 61.9687)" class="st4" cx="42.7" cy="42.8" rx="27.2" ry="13.6"/>
<path class="st3" d="M30.6,18.2c6.7-3.3,17.6,4.9,24.2,18.3s6.6,27-0.1,30.4S37.2,62,30.5,48.6"/>
<path class="st2" d="M69.8,42.8c0,7.5-12.2,13.6-27.2,13.6s-27.2-6.1-27.2-13.6"/>
</g>
<g id="Layer_x0020_1_3_">
<g id="_1421344023328_3_">
<path class="st0" d="M50.1,40.8L50.1,40.8c0.3-2.2-1.3-3.3-3.6-4.1l0.7-2.9l-1.8-0.4l-0.7,2.8c-0.5-0.1-0.9-0.2-1.4-0.3l0.7-2.9
l-1.8-0.4l-0.7,2.9c-0.4-0.1-0.8-0.2-1.1-0.3v0L38,34.6l-0.5,1.9c0,0,1.3,0.3,1.3,0.3c0.7,0.2,0.8,0.6,0.8,1l-0.8,3.3
c0,0,0.1,0,0.2,0.1c-0.1,0-0.1,0-0.2,0l-1.2,4.7c-0.1,0.2-0.3,0.5-0.8,0.4c-0.1,0-1.3-0.3-1.3-0.3l-0.9,2l2.3,0.6
c0.4,0.1,0.8,0.2,1.3,0.3l-0.7,3l1.8,0.4l0.7-2.9c0.5,0.1,0.9,0.3,1.4,0.4l-0.7,2.9l1.8,0.4l0.7-2.9c3,0.6,5.3,0.3,6.3-2.4
c0.8-2.2,0-3.5-1.6-4.3C49,43.1,49.9,42.4,50.1,40.8z M46.1,46.5c-0.5,2.2-4.3,1-5.5,0.7l1-3.9C42.8,43.6,46.6,44.2,46.1,46.5z
M46.6,40.8c-0.5,2-3.6,1-4.6,0.7l0.9-3.5C43.9,38.2,47.2,38.7,46.6,40.8z"/>
<path class="st0" d="M50.1,40.8L50.1,40.8c0.3-2.2-1.3-3.3-3.6-4.1l0.7-2.9l-1.8-0.4l-0.7,2.8c-0.5-0.1-0.9-0.2-1.4-0.3l0.7-2.9
l-1.8-0.4l-0.7,2.9c-0.4-0.1-0.8-0.2-1.1-0.3v0L38,34.6l-0.5,1.9c0,0,1.3,0.3,1.3,0.3c0.7,0.2,0.8,0.6,0.8,1l-0.8,3.3
c0,0,0.1,0,0.2,0.1c-0.1,0-0.1,0-0.2,0l-1.2,4.7c-0.1,0.2-0.3,0.5-0.8,0.4c-0.1,0-1.3-0.3-1.3-0.3l-0.9,2l2.3,0.6
c0.4,0.1,0.8,0.2,1.3,0.3l-0.7,3l1.8,0.4l0.7-2.9c0.5,0.1,0.9,0.3,1.4,0.4l-0.7,2.9l1.8,0.4l0.7-2.9c3,0.6,5.3,0.3,6.3-2.4
c0.8-2.2,0-3.5-1.6-4.3C49,43.1,49.9,42.4,50.1,40.8z M46.1,46.5c-0.5,2.2-4.3,1-5.5,0.7l1-3.9C42.8,43.6,46.6,44.2,46.1,46.5z
M46.6,40.8c-0.5,2-3.6,1-4.6,0.7l0.9-3.5C43.9,38.2,47.2,38.7,46.6,40.8z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

-11
View File
@@ -1,11 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Logo Mark">
<rect width="47.86" height="48" rx="23.93" fill="#0DBD8B"/>
<g id="Union">
<path d="M21.3075 9.42871C20.3396 9.42871 19.5549 10.214 19.5549 11.1828C19.5549 12.1516 20.3396 12.9369 21.3075 12.9369C25.9321 12.9369 29.6811 16.689 29.6811 21.3175C29.6811 22.2863 30.4657 23.0716 31.4337 23.0716C32.4016 23.0716 33.1863 22.2863 33.1863 21.3175C33.1863 14.7515 27.868 9.42871 21.3075 9.42871Z" fill="white"/>
<path d="M38.4591 21.3174C38.4591 20.3486 37.6745 19.5633 36.7065 19.5633C35.7386 19.5633 34.9539 20.3486 34.9539 21.3174C34.9539 25.9459 31.2049 29.698 26.5804 29.698C25.6124 29.698 24.8277 30.4833 24.8277 31.4521C24.8277 32.4209 25.6124 33.2062 26.5804 33.2062C33.1408 33.2062 38.4591 27.8834 38.4591 21.3174Z" fill="white"/>
<path d="M28.3329 36.8173C28.3329 37.786 27.5482 38.5714 26.5803 38.5714C20.0198 38.5714 14.7015 33.2486 14.7015 26.6826C14.7015 25.7138 15.4862 24.9285 16.4541 24.9285C17.4221 24.9285 18.2067 25.7138 18.2067 26.6826C18.2067 31.3111 21.9557 35.0632 26.5803 35.0632C27.5482 35.0632 28.3329 35.8485 28.3329 36.8173Z" fill="white"/>
<path d="M9.40112 26.6827C9.40112 27.6514 10.1858 28.4368 11.1537 28.4368C12.1217 28.4368 12.9064 27.6514 12.9064 26.6827C12.9064 22.0542 16.6553 18.3021 21.2799 18.3021C22.2478 18.3021 23.0325 17.5167 23.0325 16.548C23.0325 15.5792 22.2478 14.7939 21.2799 14.7939C14.7194 14.7939 9.40112 20.1167 9.40112 26.6827Z" fill="white"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-23
View File
@@ -1,23 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<!-- Background circle -->
<circle cx="128" cy="128" r="120" fill="#5B21B6"/>
<!-- Outer signal arc (top-left) -->
<path d="M60 128 A68 68 0 0 1 128 60" stroke="#A78BFA" stroke-width="14" stroke-linecap="round" fill="none"/>
<!-- Outer signal arc (top-right) -->
<path d="M128 60 A68 68 0 0 1 196 128" stroke="#A78BFA" stroke-width="14" stroke-linecap="round" fill="none"/>
<!-- Middle signal arc (top-left) -->
<path d="M82 128 A46 46 0 0 1 128 82" stroke="#C4B5FD" stroke-width="12" stroke-linecap="round" fill="none"/>
<!-- Middle signal arc (top-right) -->
<path d="M128 82 A46 46 0 0 1 174 128" stroke="#C4B5FD" stroke-width="12" stroke-linecap="round" fill="none"/>
<!-- Inner signal arc (top-left) -->
<path d="M104 128 A24 24 0 0 1 128 104" stroke="#EDE9FE" stroke-width="10" stroke-linecap="round" fill="none"/>
<!-- Inner signal arc (top-right) -->
<path d="M128 104 A24 24 0 0 1 152 128" stroke="#EDE9FE" stroke-width="10" stroke-linecap="round" fill="none"/>
<!-- Center antenna dot -->
<circle cx="128" cy="128" r="14" fill="#F5F3FF"/>
<!-- Antenna mast -->
<rect x="122" y="128" width="12" height="52" rx="4" fill="#DDD6FE"/>
<!-- Base platform -->
<rect x="96" y="176" width="64" height="12" rx="6" fill="#7C3AED"/>
<rect x="108" y="188" width="40" height="10" rx="5" fill="#6D28D9"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg width="280px" height="280px" style="background-color:#ffffff00" version="1.1" viewBox="0 0 280 280" xmlns="http://www.w3.org/2000/svg">
<path id="Ellipse" d="m7 140c0-74 60-134 134-134s134 60 134 134-60 134-134 134-134-60-134-134z" fill="#7e1af7"/>
<path d="m161 52c-8 21-16 43-25 65 0 0 0 3 3 3h65s0 2 2 3l-96 106c-2-2-2-3-2-5l33-72v-6h-67v-6l81-89h5z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 382 B

-1
View File
@@ -1 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny-ps" viewBox="0 0 241 241" xml:space="preserve"><title>Mempool Holdings S.A. de C.V.</title><g><g><path fill="#2E3349" d="M241.37,211.23c0,16.56-13.43,29.99-29.99,29.99H30.36c-16.56,0-29.99-13.43-29.99-29.99V30.21 c0-16.56,13.43-29.99,29.99-29.99h181.02c16.56,0,29.99,13.43,29.99,29.99V211.23z"></path><linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="120.8689" y1="68.6556" x2="120.8689" y2="301.1491"><stop offset="0" stop-color="#AE61FF"></stop><stop offset="1" stop-color="#13EFD8"></stop></linearGradient><path fill="url(#SVGID_1_)" d="M0.32,120.99v90.24c0,16.56,13.49,29.99,30.14,29.99h180.82c16.64,0,30.13-13.43,30.13-29.99 v-90.24H0.32z"></path></g><g><path fill="#FFFFFF" fill-opacity="0.3" d="M212.72,209c0,3.7-2.53,6.7-5.65,6.7h-31.24c-3.12,0-5.65-3-5.65-6.7V32.44c0-3.7,2.53-6.7,5.65-6.7h31.24 c3.12,0,5.65,3,5.65,6.7V209z"></path></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

-1
View File
@@ -1 +0,0 @@
<svg width="32" height="32" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><rect width="32" height="32" rx="5" ry="5" fill="#0082c9"/><g transform="matrix(.12 0 0 .12 .64 8.32)" fill="none" stroke="#fff" stroke-width="22"><circle cx="40" cy="64" r="26"/><circle cx="216" cy="64" r="26"/><circle cx="128" cy="64" r="46"/></g></svg>

Before

Width:  |  Height:  |  Size: 353 B

-13
View File
@@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<!-- Shield -->
<path d="M128 20L32 60v68c0 55.2 40.8 106.8 96 120 55.2-13.2 96-64.8 96-120V60L128 20z" fill="#1565C0"/>
<path d="M128 32L44 68v60c0 49.6 36 95.6 84 108V32z" fill="#1E88E5"/>
<!-- Key head (circle) -->
<circle cx="128" cy="108" r="32" fill="#FFC107"/>
<circle cx="128" cy="108" r="16" fill="#1565C0"/>
<!-- Key shaft -->
<rect x="122" y="136" width="12" height="56" rx="2" fill="#FFC107"/>
<!-- Key teeth -->
<rect x="134" y="160" width="16" height="8" rx="2" fill="#FFC107"/>
<rect x="134" y="176" width="12" height="8" rx="2" fill="#FFC107"/>
</svg>

Before

Width:  |  Height:  |  Size: 677 B

-17
View File
@@ -1,17 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<!-- Monitor body -->
<rect x="28" y="40" width="200" height="140" rx="12" fill="#1A73E8"/>
<!-- Screen -->
<rect x="40" y="52" width="176" height="108" rx="4" fill="#E8F0FE"/>
<!-- Screen content - window bars -->
<rect x="52" y="64" width="100" height="12" rx="2" fill="#4285F4"/>
<rect x="52" y="84" width="152" height="8" rx="2" fill="#DADCE0"/>
<rect x="52" y="100" width="152" height="8" rx="2" fill="#DADCE0"/>
<rect x="52" y="116" width="120" height="8" rx="2" fill="#DADCE0"/>
<!-- Cursor arrow -->
<path d="M168 88l24 56-12 4-8-18-14 16-6-6 14-16-18-8z" fill="#34A853"/>
<!-- Stand -->
<rect x="108" y="180" width="40" height="20" fill="#1A73E8"/>
<!-- Base -->
<rect x="80" y="200" width="96" height="12" rx="6" fill="#1A73E8"/>
</svg>

Before

Width:  |  Height:  |  Size: 864 B

-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="610px" height="524px" viewBox="0 0 610 524" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>RTL-Logo-Single</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1" points="0.451852397 0.2573 52.6803 0.2573 52.6803 52 0.451852397 52"></polygon>
</defs>
<g id="RTL-Logo-Single" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="BY_-_RTL_logo_wht" transform="translate(58.000000, 54.000000)">
<g id="Group-3" transform="translate(0.000000, 0.737000)" fill="#FFFFFE">
<path d="M360.6201,52.8608 C362.5721,57.4638 363.3541,61.1008 363.2471,63.1018 C355.6871,58.5468 344.3161,54.6748 338.1131,53.5648 C334.0751,52.8408 347.2931,49.6338 360.6201,52.8608 M371.6051,222.1978 C373.9431,221.7028 376.2931,221.2298 378.6451,220.7688 C381.1041,220.2858 383.5691,219.8228 386.0431,219.3858 C384.0591,218.1358 382.0681,216.8978 380.0641,215.6788 C354.6091,200.1908 327.4751,187.0398 300.6651,174.3118 C283.7451,166.2788 266.5131,158.2938 248.9661,151.0968 C264.2721,130.9078 283.8701,113.7738 304.8171,99.0968 C304.8351,99.0848 304.9201,99.0258 305.0521,98.9328 C309.6371,105.1898 315.4331,109.4478 318.3391,111.0478 C324.0201,114.1778 331.1751,117.1028 337.7531,117.9968 C350.6891,119.7558 361.3701,119.4848 373.1871,117.4478 C374.2521,117.2648 375.3251,117.1348 376.3931,116.9568 C383.9711,115.6898 388.8201,115.4978 393.2591,115.4148 C399.1381,115.3048 402.0501,116.5498 402.7421,116.7298 C404.8421,117.2728 406.6271,118.1718 407.6191,119.3778 C408.7711,120.8458 410.0021,122.2308 411.3211,123.4988 C415.2771,127.4358 419.2051,129.6158 424.9041,130.2408 C432.2391,131.0448 437.4311,128.1758 441.1021,123.4128 C442.8361,121.1638 442.0011,117.6318 441.8111,116.9388 C440.8481,113.3958 438.6251,108.2418 437.0161,103.4438 C436.5991,102.2038 434.5781,98.3918 432.1461,95.8048 C431.3431,94.9488 430.5351,94.0948 429.7281,93.2418 C425.9511,89.2468 422.1441,85.2818 418.3701,81.2898 C403.3111,65.3718 386.9861,50.7158 370.1561,36.7028 C368.4121,35.2508 366.6651,33.7948 364.9151,32.3418 C363.6461,31.2868 362.3721,30.2378 361.1001,29.1868 C360.4531,28.6528 359.8071,28.1198 359.1581,27.5868 C357.7891,26.4608 356.4161,25.3428 355.0401,24.2258 C354.4971,23.7858 353.9521,23.3488 353.4081,22.9098 C352.5511,22.2198 350.5951,20.5728 348.9481,19.2538 C354.4071,13.8118 358.8121,10.4238 366.6311,5.3008 C367.7521,4.5668 373.7921,1.3448 373.5791,0.5268 C373.4511,0.0328 363.5341,0.1448 352.1711,1.6088 C348.1791,2.1228 325.8041,5.4268 310.4411,8.7748 C294.0871,12.3378 276.8181,17.3188 260.9121,22.3258 C214.9671,36.7898 171.0961,56.3918 130.9221,83.0318 C107.8341,98.3418 86.2221,115.2368 65.3621,133.4398 C55.6411,141.9208 45.9791,150.5378 36.5451,159.3928 C35.0671,160.7798 33.6011,162.1788 32.1351,163.5788 C34.1441,163.9548 36.1521,164.3478 38.1561,164.7598 C54.7691,168.1718 71.2001,172.7318 87.1751,177.2978 C106.7131,182.8818 126.4791,188.9018 145.5921,196.2918 C134.7011,206.6528 124.0761,217.2688 113.9611,228.5028 C94.9141,249.6538 77.1581,271.9988 60.5891,295.1368 C38.9991,325.2848 20.5681,357.7988 4.5021,391.3058 C2.9801,394.4758 1.4761,397.6528 0.0001,400.8388 C2.9721,398.4598 5.9621,396.1018 8.9741,393.7648 C34.9841,373.5938 62.4081,354.9428 89.7891,336.9238 C128.3171,311.5688 169.6651,290.6218 212.5711,273.7668 C264.2991,253.4468 317.1251,233.7338 371.6051,222.1978" id="Fill-1"></path>
</g>
<g id="Group-24" transform="translate(172.000000, 352.737000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-23"></g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

-111
View File
@@ -1,111 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with Vectornator (http://vectornator.io/) -->
<svg stroke-miterlimit="10" style="fill-rule:nonzero;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;" version="1.1" viewBox="8.789 8.791 32.422 32.418" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Untitled">
<path d="M38.8519 23.6353L38.3818 24.2217L41.2105 25.0446L41.2105 24.8132L39.3844 23.4968L38.8519 23.6353Z" fill="#d3d3d3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M37.9843 25.2648L38.7188 25.2376L40.119 25.1551L41.2106 25.0446L38.3819 24.2217L37.5381 23.9764L37.9843 25.2648Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M38.8519 23.6353L38.3818 24.2217L37.5381 23.9764L38.0403 23.846L38.8519 23.6353Z" fill="#ababab" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M37.9843 25.2648L33.5662 25.2648L36.0929 24.3388L37.9843 25.2648Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M38.0403 23.846L37.145 23.3355L36.8724 23.18L38.7803 21.9829L39.3844 23.4968L38.8519 23.6353L38.0403 23.846Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M37.0092 23.5521L36.7613 23.8298L36.3913 23.3554L36.3959 23.3517L37.0092 23.5521Z" fill="#ffffff" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.8724 23.1801L37.1451 23.3355L37.0092 23.5521L36.3959 23.3517L36.6823 23.1455L36.8724 23.1801Z" fill="#ffffff" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M38.7803 21.9829L36.8724 23.18L36.2058 20.8919L38.7665 21.949L38.7803 21.9829Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.7613 23.8298L37.0092 23.5521L37.1451 23.3355L38.0403 23.846L37.5381 23.9764L36.7172 23.8792L36.7613 23.8298Z" fill="#454545" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.3913 23.3554L36.7613 23.8298L36.7172 23.8792L35.9653 23.6626L36.3913 23.3554Z" fill="#808080" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.7172 23.8792L37.5381 23.9764L36.103 24.3351L36.069 24.3278L36.0681 24.3271L33.4046 23.7318L35.9653 23.6626L36.7172 23.8792Z" fill="#d3d3d3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.3958 23.3517L36.3913 23.3554L36.1535 23.0497L36.6823 23.1455L36.3958 23.3517Z" fill="#efefef" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.1535 23.0497L36.3913 23.3554L35.9653 23.6626L35.9139 23.2419L36.1535 23.0497Z" fill="#d4d4d4" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.2058 20.8919L36.8724 23.1801L36.6823 23.1454L36.1535 23.0497L35.6779 22.9627L36.1342 20.8956L36.2058 20.8919Z" fill="#d8d8d8" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.1342 20.8956L35.6779 22.9627L34.4972 20.9745L36.1342 20.8956Z" fill="#bcbcbc" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.103 24.3351L37.5381 23.9764L37.9843 25.2648L36.0929 24.3388L36.103 24.3351Z" fill="#d8d8d8" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M36.0928 24.3388L33.5661 25.2648L33.5092 25.2648L33.3183 23.734L33.4046 23.7318L36.0681 24.3271L36.069 24.3278L36.0928 24.3388Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M33.5092 25.2648L35.7366 27.2015L35.3005 27.5949L35.2785 27.6664L35.2638 27.6745L31.5591 26.1025L33.5092 25.2648Z" fill="#d3d3d3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M34.4972 20.9745L35.6779 22.9627L33.3202 23.7046L34.4972 20.9745Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M35.2748 27.6789L34.8397 29.0874L34.0712 31.1582L31.3848 29.8823L35.2639 27.6745L35.2748 27.6789Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M35.2638 27.6745L31.3847 29.8823L31.5591 26.1025L35.2638 27.6745Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M33.2366 23.6942L31.0303 23.4143L32.3322 22.3697L32.705 22.0706L34.4972 20.9744L33.3202 23.7045L33.2366 23.6942Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M34.0712 31.1582L33.9959 31.2459L33.995 31.2459L29.0407 31.307L29.0398 31.307L29.0306 31.2916L29.0003 31.2399L31.3848 29.8823L34.0712 31.1582Z" fill="#d3d3d3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M33.9949 31.2459L32.3276 33.1745L30.6869 33.9126L29.0407 31.307L33.9949 31.2459Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M33.5661 25.2648L37.9842 25.2648L37.112 25.8822L36.5152 26.4988L35.7366 27.2015L33.5092 25.2648L33.5661 25.2648Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M33.3183 23.734L33.5092 25.2648L31.5591 26.1024L31.0303 23.4143L33.2366 23.6942L33.3155 23.7119L33.3183 23.734Z" fill="#bcbcbc" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M33.3183 23.734L33.4046 23.7318L33.3156 23.712L33.3183 23.734Z" fill="#3a3a3a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M35.6779 22.9627L36.1535 23.0497L35.9139 23.2419L35.9653 23.6626L33.4046 23.7318L33.3156 23.7119L33.2366 23.6942L33.3202 23.7046L35.6779 22.9627Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<g opacity="1">
<path d="M34.0372 36.0387C34.0712 36.1212 34.0023 36.4077 34.0023 36.4077L33.5773 36.8313L33.6938 36.4622L33.8655 36.1594L33.6598 35.7359L32.671 35.3941L32.9253 35.7904L33.1998 36.4077L32.8904 36.6L32.4305 37.0795L32.5682 36.628L32.8904 36.2972C32.8904 36.2972 32.705 35.9834 32.671 35.8729C32.6361 35.7631 32.0394 35.3661 32.0394 35.3661L31.8677 35.1186L32.8106 35.0124L32.8115 35.0124L34.14 35.3116L34.14 35.8729L33.9683 35.5038L33.3706 35.3388L33.8655 35.6526C33.8655 35.6526 34.0023 35.9554 34.0372 36.0387" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M32.8106 35.0124L31.8677 35.1186L30.2958 34.3251L32.4994 34.9425L32.8106 35.0124Z" fill="#7f7f7f" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M31.9016 15.918L32.4305 17.3559L30.3894 21.9646L29.6981 23.5248L29.9561 20.9605L30.0994 19.5358L30.2352 18.1869L30.2958 18.2348L30.2728 18.1354L31.9016 15.918Z" fill="#585858" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M32.4305 17.3559L31.9016 20.8647L31.0303 23.4143L30.3894 21.9645L32.4305 17.3559Z" fill="#545454" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M31.9016 20.8647L32.3322 22.3697L31.0303 23.4143L31.9016 20.8647Z" fill="#454545" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M31.9016 15.918L30.2728 18.1354L29.89 16.4808L29.89 16.4801L29.935 14.4225C30.3986 14.7769 30.6943 15.0111 30.7219 15.0539C30.8724 15.2851 31.9016 15.918 31.9016 15.918" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M31.8677 35.1186L30.8935 34.9145L29.5613 34.4467L30.2958 34.3251L31.8677 35.1186Z" fill="#d3d3d3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M31.5592 26.1024L31.3848 29.8823L27.8912 29.3739L27.1521 28.1289L31.5142 26.123L31.5592 26.1024Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M31.4894 26.0891L31.5141 26.123L27.1521 28.1289L29.3363 26.07L29.3896 26.0199L31.4894 26.0891Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M31.3848 29.8823L29.0003 31.2399L27.8912 29.374L31.3848 29.8823Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M30.7898 36.0659L30.9275 35.5428L31.8677 35.1186L32.0394 35.3661L31.1331 35.6806L31.0303 36.0107L31.202 36.4077L30.7898 36.0659Z" fill="#7c7c7c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M30.3894 21.9645L31.0303 23.4143L29.6981 23.5248L30.3894 21.9645Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.5282 24.895L29.6981 23.5248L31.0303 23.4143L29.3987 25.9462L29.5282 24.895Z" fill="#454545" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M30.6869 33.9126L30.2958 34.3251L29.5613 34.4467L28.6496 34.0754L30.6869 33.9126Z" fill="#7c7c7c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M30.159 37.7234L29.7669 37.162L29.2179 36.804L30.7181 37.0648L30.9615 37.2445C30.9615 37.2445 31.236 37.5584 31.2709 37.6961C31.3048 37.8339 31.3737 38.1477 31.3737 38.2582C31.3737 38.368 30.8247 38.7098 30.8247 38.7098L31.0992 38.396L30.9275 37.7234L30.2269 37.327C30.2269 37.327 30.5703 38.038 30.6043 38.1477C30.6392 38.2582 30.4675 38.5168 30.3298 38.5448C30.1976 38.5713 29.7936 38.8564 29.7679 38.874L30.2958 38.2582L30.159 37.7234Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M30.7181 37.0648L29.2179 36.804L29.127 36.1167L29.1353 36.1211L29.5952 36.3252L30.3298 36.7768L30.7181 37.0648Z" fill="#7c7c7c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M30.2352 18.1869L30.0994 19.5358L22.4568 15.5216L20.4039 14.2052L26.5673 16.175L30.181 18.1449L30.1828 18.1456L30.2352 18.1869Z" fill="#9c9c9c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M30.181 18.1449L26.5673 16.175L22.5045 13.9599L20.9189 12.4372L20.2533 11.7653L20.2533 11.3137L20.6444 11.0935L22.353 11.9966L30.181 18.1449Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M30.0993 19.5358L29.9561 20.9605L23.1912 17.2491L23.1224 17.2344L20.9189 15.918L20.816 15.2741L21.1383 15.0811L22.4567 15.5216L30.0993 19.5358Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.935 14.4225L29.89 16.4801L29.89 16.4808L29.8881 16.5773L22.4788 10.1284L29.935 14.4225Z" fill="#9c9c9c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.935 14.4225L22.4788 10.1284L21.6874 9.07799L21.9417 8.79141L22.5596 8.95646C22.5596 8.95646 27.8866 12.8571 29.935 14.4225" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M22.5247 10.1904L22.4789 10.1284L29.8882 16.5773L29.89 16.4808L30.2728 18.1353L30.2352 18.1869L30.1829 18.1457L30.181 18.1449L22.3531 11.9966L21.0218 10.5042L20.884 9.97009L21.3789 9.63932L22.5247 10.1904Z" fill="#585858" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.6871 23.5197L23.9258 19.2617L23.1564 18.6171L22.7598 18.069L22.7607 18.069L29.9561 20.9605L29.6981 23.5248L29.6871 23.5197Z" fill="#545454" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.6981 23.5248L29.5282 24.895L26.0605 21.9771L29.6871 23.5197L29.6981 23.5248Z" fill="#454545" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.6871 23.5197L26.0605 21.9771L24.1452 20.3851L23.5485 19.658L23.5135 19.1792L23.9258 19.2617L29.6871 23.5197Z" fill="#3a3a3a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M31.4894 26.0891L29.3896 26.0199L29.3987 25.9462L31.0303 23.4143L31.5592 26.1024L31.5141 26.123L31.4894 26.0891Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.3896 26.0199L29.3364 26.07L29.2399 26.067L29.3896 26.0199Z" fill="#a3a3a3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.3896 26.0199L29.2399 26.067L23.1637 25.8792L24.8265 22.8566L29.3896 26.0199Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.3363 26.07L27.152 28.129L24.237 29.1773L24.0626 29.0491L22.7451 28.9386L22.1823 28.6955L25.8198 27.1434L29.2399 26.0671L29.3363 26.07Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.2399 26.0671L25.8199 27.1434L23.1637 25.88L23.1637 25.8792L29.2399 26.0671Z" fill="#c8c8c8" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M28.9985 36.4902L28.118 35.5996L29.127 36.1167L29.2179 36.804L28.8268 37.2998L28.7579 37.7786L29.1353 38.3407L28.5862 38.01L28.4357 37.3661L28.6551 36.997L28.9985 36.4902Z" fill="#454545" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.1261 36.1108L29.127 36.1167L28.118 35.5996L28.0234 34.832L29.1261 36.1108Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M28.6147 34.0769L28.5743 34.071L29.0398 31.307L29.0407 31.307L30.6869 33.9126L28.6496 34.0754L28.6211 34.0776L28.6156 34.0769L28.6147 34.0769Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.0398 31.307L28.5743 34.071L22.8819 33.268L29.0398 31.307Z" fill="#a2a2a2" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.0398 31.307L22.8819 33.268L26.2331 30.0856L29.0398 31.307Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M29.0398 31.307L26.2331 30.0856L27.8912 29.374L29.0003 31.2399L28.9984 31.2407L29.0306 31.2916L29.0398 31.307Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M28.6211 34.0776L28.6496 34.0754L29.5613 34.4467L28.0234 34.832L28.6211 34.0776Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M28.6211 34.0776L28.0234 34.832L28.118 35.5996L28.0234 35.5038L27.2889 34.9425L28.6211 34.0776Z" fill="#d3d3d3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M28.6211 34.0776L27.2889 34.9425L26.8639 35.0361L25.9411 34.0864L28.5735 34.0769L28.6147 34.0769L28.6156 34.0769L28.6211 34.0776Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M28.5743 34.071L28.5734 34.0769L25.9411 34.0864L25.9383 34.0864L23.2858 34.0953L23.2849 34.0953L22.8819 33.268L28.5743 34.071Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M27.1521 28.129L27.8912 29.374L26.2331 30.0856L27.1521 28.129Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M26.0862 35.1288L26.8639 35.036C26.8639 35.036 24.7972 38.9285 24.3619 39.7816L26.0862 35.1288Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M22.5045 13.9599L26.5673 16.175L20.4039 14.2052L20.1156 13.6711L20.2533 13.2858L20.7472 13.2858L22.5596 14.0129L22.5045 13.9599Z" fill="#7c7c7c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M26.2331 30.0856L22.8819 33.268L23.5135 31.1309L24.2232 29.2148L24.2407 29.2192L26.2331 30.0856Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M26.0862 35.1288L24.3619 39.7816C24.3142 39.8759 24.2867 39.9326 24.283 39.9437C24.2481 40.0535 23.5485 40.5603 23.5485 40.5603L22.848 40.5876L26.0862 35.1288Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M25.9383 34.0864L25.9411 34.0864L26.8638 35.0361L26.0861 35.1288L24.5565 35.3116L25.9383 34.0864Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M25.9383 34.0864L24.5565 35.3116L24.1076 35.0559L23.6164 34.7767L23.2849 34.0961L23.2858 34.0953L25.9383 34.0864Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M25.8199 27.1434L22.1823 28.6955L22.1814 28.6955L22.0445 28.6358L21.5368 28.1784L23.1637 25.88L25.8199 27.1434Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.4198 22.5745L24.8265 22.8566L21.0998 22.6128L19.7033 22.5214L19.6308 22.506L16.4063 19.8805L22.6845 21.415L24.4198 22.5745Z" fill="#bcbcbc" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.5565 35.3116L22.69 40.7334C22.5403 40.8661 22.3227 41.0399 22.2162 41.0399C22.0445 41.0399 21.1732 41.1769 21.1732 41.1769L20.8014 41.1865L24.5565 35.3116Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.5565 35.3116L20.8014 41.1865L19.986 41.2086L19.3121 41.1496L19.1809 40.9906L24.1076 35.0559L24.5565 35.3116Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.0552 20.3608L24.4197 22.5745L22.6844 21.415L19.0744 19.0024L24.0552 20.3608Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.2407 29.2192L24.2233 29.2148L24.237 29.1772L27.1521 28.1289L26.2331 30.0856L24.2407 29.2192Z" fill="#8d8d8d" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.1452 20.3852L26.0605 21.9771L29.5283 24.895L29.3988 25.9463L29.3896 26.02L24.8265 22.8567L24.4198 22.5745L24.0552 20.3608L24.1452 20.3852Z" fill="#595959" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.1076 35.0559L19.1809 40.9905L18.5777 41.0399L17.8092 40.8078L17.5347 40.2848L24.1076 35.0559Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.1076 35.0559L17.5347 40.2848L23.2849 34.0961L23.6164 34.7767L24.1076 35.0559Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M23.2858 34.0953L23.2849 34.0961L23.2849 34.0953L23.2858 34.0953Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M23.2849 34.0953L23.2849 34.0961L16.3603 39.3853L19.0036 36.8313L22.8819 33.268L23.2849 34.0953Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M23.2849 34.0961L17.5346 40.2848L16.869 40.3673L16.2373 40.0535L16.3062 39.4369L16.3585 39.3861L16.3603 39.3853L23.2849 34.0961Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M24.8265 22.8567L23.1637 25.8792L23.1628 25.8792L23.138 25.8674L21.0998 22.6128L24.8265 22.8567Z" fill="#8c8c8c" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M23.1628 25.8792L23.1637 25.8792L23.1637 25.88L21.5368 28.1784L21.4817 28.129L20.4039 28.157L19.9788 27.9368L19.5187 27.5677L19.1754 27.1434L19.2699 27.1132L23.1628 25.8792Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M23.138 25.8674L23.1628 25.8792L19.2698 27.1132L20.3441 24.5385L23.138 25.8674Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M22.8479 40.5876C22.8479 40.5876 22.7818 40.6524 22.69 40.7334L24.5565 35.3116L26.0861 35.1288L22.8479 40.5876Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M22.7102 18.0005L22.6762 17.2071L23.1224 17.2344L23.1913 17.2491L29.9561 20.9605L22.7606 18.0691L22.7598 18.0691L22.7102 18.0005Z" fill="#454545" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M19.0744 19.0024L22.6844 21.415L16.4063 19.8805L11.2674 17.3287L19.0734 19.0024L19.0744 19.0024Z" fill="#d3d3d3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M20.3442 24.5385L20.256 24.5385L14.6995 24.5385C14.6995 24.5385 12.7365 23.6626 12.7016 23.5794C12.6676 23.4969 12.3591 22.9628 12.3591 22.9628L12.4271 22.6048L17.9763 23.6118L18.0745 23.6295L20.3442 24.5385Z" fill="#d3d3d3" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M20.3442 24.5385L19.2699 27.1132L19.1754 27.1434L18.3381 27.0881L17.6724 26.8958L17.2464 26.526L17.0885 26.0104L20.256 24.5385L20.3442 24.5385Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M20.256 24.5385L17.0884 26.0104L17.0747 25.9647L15.2971 25.7717L14.8712 25.2376L14.6995 24.5385L20.256 24.5385Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M16.4063 19.8805L19.6307 22.506L19.5114 22.4802L11.24 19.1166L10.0381 18.3305L9.06393 17.3007L8.82343 16.673L8.99512 16.3703L9.86642 16.7666L11.2675 17.3287L16.4063 19.8805Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M17.9763 23.6117L12.4271 22.6047L10.8552 22.2356L19.2993 22.4353L19.5114 22.4802L19.6307 22.506L19.7033 22.5214L17.9763 23.6117Z" fill="#707070" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M17.9763 23.6117L19.7033 22.5214L21.0998 22.6128L23.1381 25.8674L20.3442 24.5385L18.0745 23.6294L17.9763 23.6117Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M19.0735 19.0024L11.2675 17.3287L9.45504 15.5216L9.38617 15.0538L9.62673 14.8226L12.4959 16.5625C12.4959 16.5625 16.2373 18.0278 16.443 18.1383C16.6487 18.248 18.1664 18.7549 18.1664 18.7549L19.0735 19.0024Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M19.2993 22.4353L11.463 20.7601L11.542 20.7704L9.59278 19.9608L8.99511 19.2617L8.78939 18.6724L9.06398 18.4411L11.3354 19.1792L11.24 19.1166L19.5114 22.4802L19.2993 22.4353Z" fill="#9a9a9a" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M11.4621 20.7601L11.4631 20.7601L19.2993 22.4353L10.8552 22.2357L10.073 21.5918L9.79847 20.8367L9.90129 20.5502L11.4621 20.7601Z" fill="#484848" fill-rule="evenodd" opacity="1" stroke="none"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 19 KiB

-5
View File
@@ -1,5 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M 30,2.0000001 V 30 h -1 -2 v 2 h 5 V -3.3333334e-8 L 27,0 v 2 z"/>
<path fill="#ffffff" d="M 9.9515939,10.594002 V 12.138 h 0.043994 c 0.3845141,-0.563728 0.8932271,-1.031728 1.4869981,-1.368 0.580003,-0.322998 1.244999,-0.485 1.993002,-0.485 0.72,0 1.376999,0.139993 1.971998,0.42 0.595,0.279004 1.047001,0.771001 1.355002,1.477001 0.338003,-0.500001 0.795999,-0.941 1.376999,-1.323001 0.579999,-0.382998 1.265998,-0.574 2.059998,-0.574 0.602003,0 1.160002,0.074 1.674002,0.220006 0.514,0.148006 0.953998,0.382998 1.321999,0.706998 0.36601,0.322999 0.653001,0.746 0.859,1.268002 0.205001,0.521998 0.307994,1.15 0.307994,1.887001 v 7.632997 h -3.127 v -6.463997 c 0,-0.383002 -0.01512,-0.743002 -0.04399,-1.082003 -0.02079,-0.3072 -0.103219,-0.607113 -0.242003,-0.881998 -0.133153,-0.25081 -0.335962,-0.457777 -0.584001,-0.596002 -0.257008,-0.146003 -0.605998,-0.220006 -1.046997,-0.220006 -0.440002,0 -0.796003,0.085 -1.068,0.253002 -0.272013,0.170003 -0.485001,0.390002 -0.639001,0.662003 -0.159119,0.287282 -0.263585,0.601602 -0.307994,0.926997 -0.05197,0.346923 -0.07801,0.697217 -0.07801,1.048002 v 6.353999 h -3.128005 v -6.398 c 0,-0.338003 -0.0072,-0.673001 -0.02116,-1.004001 -0.01134,-0.313663 -0.07487,-0.623229 -0.187994,-0.915999 -0.107943,-0.276623 -0.300435,-0.512126 -0.550001,-0.673001 -0.25799,-0.168 -0.636,-0.253002 -1.134999,-0.253002 -0.198123,0.0083 -0.394383,0.04195 -0.584002,0.100006 -0.258368,0.07446 -0.498455,0.201827 -0.704999,0.373985 -0.227981,0.183987 -0.421999,0.449 -0.583997,0.794003 -0.161008,0.345978 -0.242003,0.797998 -0.242003,1.356998 v 6.618999 H 6.99942 V 10.590001 Z"/>
<path fill="#ffffff" d="M 2,2.0000001 V 30 h 3 v 2 H 0 V 9.2650922e-8 L 5,0 v 2 z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
<stop stop-color="#420C5D" offset="0%"></stop>
<stop stop-color="#951AD1" offset="100%"></stop>
</linearGradient>
<path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path>
<filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3">
<feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="tor-browser-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon_512x512">
<g id="Group">
<g id="tb_icon/Stable">
<g id="Stable">
<circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle>
<path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,155.463547 256.525143,155.17259 L256.525143,124.146588 C329.115485,124.430449 387.881799,183.338693 387.881799,255.992903 C387.881799,328.654211 329.115485,387.562455 256.525143,387.846316 L256.525143,356.820314 Z M256.525143,201.718689 C286.266674,202.00255 310.3026,226.180407 310.3026,255.992903 C310.3026,285.812497 286.266674,309.990353 256.525143,310.274214 L256.525143,201.718689 Z M0,255.992903 C0,397.384044 114.60886,512 256,512 C397.384044,512 512,397.384044 512,255.992903 C512,114.60886 397.384044,0 256,0 C114.60886,0 0,114.60886 0,255.992903 Z" id="center" fill="url(#linearGradient-1)"></path>
<g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) ">
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
<use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

-236
View File
@@ -1,236 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="128px"
height="128px"
viewBox="0 0 128 128"
version="1.1"
id="svg96"
sodipodi:docname="Sovran_SystemsOS_Updater_Iconv3.svg"
xml:space="preserve"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview98"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="5.2149125"
inkscape:cx="9.0126153"
inkscape:cy="64.430611"
inkscape:window-width="3440"
inkscape:window-height="1352"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><defs
id="defs67"><linearGradient
inkscape:collect="always"
id="linearGradient936"><stop
style="stop-color:#1e8e11;stop-opacity:1;"
offset="0"
id="stop932" /><stop
style="stop-color:#1bff00;stop-opacity:0;"
offset="1"
id="stop934" /></linearGradient><linearGradient
id="linearGradient1028"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop1026" /></linearGradient><linearGradient
id="linearGradient998"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop996" /></linearGradient><radialGradient
id="radial0"
gradientUnits="userSpaceOnUse"
cx="131.914749"
cy="55.927143"
fx="131.914749"
fy="55.927143"
r="160"
gradientTransform="matrix(0.232034,-0.541475,-0.368794,-0.0298398,4.277749,118.95849)"><stop
offset="0"
style="stop-color:#00ff39;stop-opacity:1;"
id="stop2" /><stop
offset="1"
style="stop-color:#004a19;stop-opacity:1;"
id="stop4" /></radialGradient><radialGradient
id="radial1"
gradientUnits="userSpaceOnUse"
cx="525.587769"
cy="638.591797"
fx="525.587769"
fy="638.591797"
r="192"
gradientTransform="matrix(-0.107656,-0.225172,-0.327748,0.258343,373.87973,30.205086)"><stop
offset="0"
style="stop-color:#43b60b;stop-opacity:1;"
id="stop7" /><stop
offset="1"
style="stop-color:#0b88ff;stop-opacity:0.00829875;"
id="stop9" /></radialGradient><clipPath
id="clip1"><path
d="M 7 46 L 57 46 L 57 93 L 7 93 Z M 7 46 "
id="path12" /></clipPath><clipPath
id="clip2"><path
d="M 32.25 46.957031 C 19.6875 46.96875 9.085938 56.636719 7.503906 69.53125 C 9.0625 82.445312 19.667969 92.144531 32.25 92.160156 C 44.816406 92.148438 55.414062 82.480469 57 69.585938 C 55.441406 56.671875 44.835938 46.972656 32.25 46.957031 Z M 32.25 46.957031 "
id="path15" /></clipPath><radialGradient
id="radial2"
gradientUnits="userSpaceOnUse"
cx="131.914749"
cy="55.927143"
fx="131.914749"
fy="55.927143"
r="160"
gradientTransform="matrix(0.485163,-1.148584,-0.771115,-0.0632965,-47.124961,203.98857)"><stop
offset="0"
style="stop-color:rgb(92.941177%,20%,23.137255%);stop-opacity:1;"
id="stop18" /><stop
offset="1"
style="stop-color:rgb(63.921571%,27.843139%,72.941178%);stop-opacity:1;"
id="stop20" /></radialGradient><radialGradient
id="radial3"
gradientUnits="userSpaceOnUse"
cx="525.587769"
cy="638.591797"
fx="525.587769"
fy="638.591797"
r="192"
gradientTransform="matrix(-0.225099,-0.477638,-0.685291,0.548001,725.67923,15.723794)"><stop
offset="0"
style="stop-color:rgb(10.980392%,44.313726%,84.705883%);stop-opacity:1;"
id="stop23" /><stop
offset="1"
style="stop-color:rgb(20.784314%,51.764709%,89.411765%);stop-opacity:0.00829876;"
id="stop25" /></radialGradient><linearGradient
id="linear0"
gradientUnits="userSpaceOnUse"
x1="22"
y1="37"
x2="62"
y2="37"
gradientTransform="matrix(1.4,0,0,1.4,-26.799973,2.491745)"><stop
offset="0"
style="stop-color:rgb(58.039218%,57.647061%,56.470591%);stop-opacity:1;"
id="stop28" /><stop
offset="0.0908155"
style="stop-color:rgb(87.058824%,86.666667%,85.490197%);stop-opacity:1;"
id="stop30" /><stop
offset="0.336093"
style="stop-color:rgb(60.392159%,60.000002%,58.823532%);stop-opacity:1;"
id="stop32" /><stop
offset="0.844326"
style="stop-color:rgb(76.47059%,75.294119%,72.941178%);stop-opacity:1;"
id="stop34" /><stop
offset="0.930505"
style="stop-color:rgb(87.058824%,86.666667%,85.490197%);stop-opacity:1;"
id="stop36" /><stop
offset="1"
style="stop-color:rgb(75.294119%,74.901962%,73.725492%);stop-opacity:1;"
id="stop38" /></linearGradient><radialGradient
id="radial4"
gradientUnits="userSpaceOnUse"
cx="-172.560638"
cy="28.569126"
fx="-172.560638"
fy="28.569126"
r="15.85742"
gradientTransform="matrix(1.560712,0,0,1.4252,300.69366,13.349996)"><stop
offset="0"
style="stop-color:rgb(100%,100%,100%);stop-opacity:0.358268;"
id="stop41" /><stop
offset="1"
style="stop-color:rgb(100%,100%,100%);stop-opacity:0.0944882;"
id="stop43" /></radialGradient><filter
id="alpha"
filterUnits="objectBoundingBox"
x="0"
y="0"
width="1"
height="1"><feColorMatrix
type="matrix"
in="SourceGraphic"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"
id="feColorMatrix46" /></filter><mask
id="mask0"><g
filter="url(#alpha)"
id="g51"><rect
x="0"
y="0"
width="128"
height="128"
style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"
id="rect49" /></g></mask><clipPath
id="clip3"><rect
x="0"
y="0"
width="192"
height="152"
id="rect54" /></clipPath><g
id="surface382"
clip-path="url(#clip3)"><path
style=" stroke:none;fill-rule:nonzero;fill:rgb(27.058825%,21.176471%,21.568628%);fill-opacity:1;"
d="M 40 59.957031 C 26.191406 59.957031 15 71.152344 15 84.957031 C 15.011719 85.996094 15.085938 86.777344 15.222656 87.804688 C 15.222656 75.957031 27.421875 65.96875 40 65.957031 C 52.597656 65.972656 64.777344 75.957031 64.777344 87.859375 C 64.917969 86.816406 64.992188 86.011719 65 84.957031 C 65 71.152344 53.808594 59.957031 40 59.957031 Z M 40 59.957031 "
id="path57" /></g><radialGradient
id="radial5"
gradientUnits="userSpaceOnUse"
cx="40"
cy="227"
fx="40"
fy="227"
r="28"
gradientTransform="matrix(0.575553,0,1.60551e-8,1.540703,8.977913,-280.78108)"><stop
offset="0"
style="stop-color:rgb(100%,100%,100%);stop-opacity:1;"
id="stop60" /><stop
offset="0.744626"
style="stop-color:rgb(98.039216%,98.039216%,98.039216%);stop-opacity:1;"
id="stop62" /><stop
offset="1"
style="stop-color:rgb(87.450981%,87.450981%,87.450981%);stop-opacity:1;"
id="stop64" /></radialGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient936"
id="linearGradient938"
x1="-48.519272"
y1="18.511358"
x2="287.07454"
y2="18.511358"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1020247,0,0,1.1097375,37.198581,-10.424856)" /></defs><path
style="fill:#f5f5f3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 20,11.957031 h 88 c 4.41797,0 8,3.582031 8,8 V 108 c 0,4.41797 -3.58203,8 -8,8 H 20 c -4.417969,0 -8,-3.58203 -8,-8 V 19.957031 c 0,-4.417969 3.582031,-8 8,-8 z m 0,0"
id="path69" /><path
style="fill:url(#radial0);fill-rule:nonzero;stroke:none"
d="m 20,85.957031 h 88 v -66 H 20 Z m 0,0"
id="path71" /><path
style="fill:none;fill-rule:nonzero;stroke:none;fill-opacity:1"
d="m 20,85.957031 h 88 v -66 H 20 Z m 0,0"
id="path73" /><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 1"
transform="matrix(0.1816,0,0,0.1816,35.224187,79.037164)"><ellipse
fill="#54c147"
cx="168.64549"
cy="10.117889"
id="circle8314"
rx="184.91634"
ry="179.91556"
style="fill:url(#linearGradient938);fill-opacity:1;stroke-width:1.71591" /><polygon
fill="#ffffff"
points="46.678,120.299 63.562,96.402 96.977,121.718 145.084,50.79 168.752,69.02 103.583,164.647 "
id="polygon8316"
transform="matrix(1.7395866,0,0,1.6925423,-18.737581,-172.19767)" /></g></svg>

Before

Width:  |  Height:  |  Size: 8.4 KiB

-267
View File
@@ -1,267 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
viewBox="0 0 256 256"
id="svg383"
sodipodi:docname="vaultwarden.svg"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
width="256"
height="256"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs387">
<mask
id="holes">
<rect
x="-60"
y="-60"
width="120"
height="120"
fill="#fff"
id="rect368" />
<circle
id="hole"
cy="-40"
r="3" />
<use
transform="rotate(72)"
xlink:href="#hole"
id="use371" />
<use
transform="rotate(144)"
xlink:href="#hole"
id="use373" />
<use
transform="rotate(216)"
xlink:href="#hole"
id="use375" />
<use
transform="rotate(-72)"
xlink:href="#hole"
id="use377" />
</mask>
</defs>
<sodipodi:namedview
id="namedview385"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="3.3359375"
inkscape:cx="128.14988"
inkscape:cy="127.85012"
inkscape:window-width="3440"
inkscape:window-height="1363"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="gear" />
<title
id="title287">Vaultwarden Icon</title>
<g
id="gear"
mask="url(#holes)"
transform="matrix(2.4381018,0,0,2.4381018,128,128)">
<path
d="M -31.1718,-33.813208 -4.675771,40.375675 H 4.6757689 L 31.171799,-33.813208 H 21.404635 L 4.6757689,13.77574 q -1.662496,4.571864 -2.805462,8.624198 -1.142966,3.948427 -1.870308,7.585137 -0.72734199,-3.63671 -1.8703079,-7.689043 -1.142966,-4.052334 -2.805462,-8.728104 L -21.30073,-33.813208 Z"
stroke="#ffffff"
stroke-width="4.51171"
id="path289"
style="fill:#ffffff;fill-opacity:1" />
<circle
transform="scale(-1,1)"
r="43"
fill="none"
stroke="#ffffff"
stroke-width="9"
id="circle291"
cx="0"
cy="0" />
<g
id="cogs"
transform="scale(-1,1)">
<polygon
id="cog"
points="46,3 51,0 46,-3 "
stroke="#ffffff"
stroke-linejoin="round"
stroke-width="3" />
<use
transform="rotate(11.25)"
xlink:href="#cog"
id="use294" />
<use
transform="rotate(22.5)"
xlink:href="#cog"
id="use296" />
<use
transform="rotate(33.75)"
xlink:href="#cog"
id="use298" />
<use
transform="rotate(45)"
xlink:href="#cog"
id="use300" />
<use
transform="rotate(56.25)"
xlink:href="#cog"
id="use302" />
<use
transform="rotate(67.5)"
xlink:href="#cog"
id="use304" />
<use
transform="rotate(78.75)"
xlink:href="#cog"
id="use306" />
<use
transform="rotate(90)"
xlink:href="#cog"
id="use308" />
<use
transform="rotate(101.25)"
xlink:href="#cog"
id="use310" />
<use
transform="rotate(112.5)"
xlink:href="#cog"
id="use312" />
<use
transform="rotate(123.75)"
xlink:href="#cog"
id="use314" />
<use
transform="rotate(135)"
xlink:href="#cog"
id="use316" />
<use
transform="rotate(146.25)"
xlink:href="#cog"
id="use318" />
<use
transform="rotate(157.5)"
xlink:href="#cog"
id="use320" />
<use
transform="rotate(168.75)"
xlink:href="#cog"
id="use322" />
<use
transform="scale(-1)"
xlink:href="#cog"
id="use324" />
<use
transform="rotate(-168.75)"
xlink:href="#cog"
id="use326" />
<use
transform="rotate(-157.5)"
xlink:href="#cog"
id="use328" />
<use
transform="rotate(-146.25)"
xlink:href="#cog"
id="use330" />
<use
transform="rotate(-135)"
xlink:href="#cog"
id="use332" />
<use
transform="rotate(-123.75)"
xlink:href="#cog"
id="use334" />
<use
transform="rotate(-112.5)"
xlink:href="#cog"
id="use336" />
<use
transform="rotate(-101.25)"
xlink:href="#cog"
id="use338" />
<use
transform="rotate(-90)"
xlink:href="#cog"
id="use340" />
<use
transform="rotate(-78.75)"
xlink:href="#cog"
id="use342" />
<use
transform="rotate(-67.5)"
xlink:href="#cog"
id="use344" />
<use
transform="rotate(-56.25)"
xlink:href="#cog"
id="use346" />
<use
transform="rotate(-45)"
xlink:href="#cog"
id="use348" />
<use
transform="rotate(-33.75)"
xlink:href="#cog"
id="use350" />
<use
transform="rotate(-22.5)"
xlink:href="#cog"
id="use352" />
<use
transform="rotate(-11.25)"
xlink:href="#cog"
id="use354" />
</g>
<g
id="mounts"
transform="scale(-1,1)">
<polygon
id="mount"
points="-7,-42 0,-35 7,-42 "
stroke="#ffffff"
stroke-linejoin="round"
stroke-width="6" />
<use
transform="rotate(72)"
xlink:href="#mount"
id="use358" />
<use
transform="rotate(144)"
xlink:href="#mount"
id="use360" />
<use
transform="rotate(-144)"
xlink:href="#mount"
id="use362" />
<use
transform="rotate(-72)"
xlink:href="#mount"
id="use364" />
</g>
</g>
<metadata
id="metadata381">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>Vaultwarden Icon</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Mathijs van Veluw</dc:title>
</cc:Agent>
</dc:creator>
<dc:relation>Rust Logo</dc:relation>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><style>.style0{fill: #0073aa;}</style><g><g><path d="M4.548 31.999c0 10.9 6.3 20.3 15.5 24.706L6.925 20.827C5.402 24.2 4.5 28 4.5 31.999z M50.531 30.614c0-3.394-1.219-5.742-2.264-7.57c-1.391-2.263-2.695-4.177-2.695-6.439c0-2.523 1.912-4.872 4.609-4.872 c0.121 0 0.2 0 0.4 0.022C45.653 7.3 39.1 4.5 32 4.548c-9.591 0-18.027 4.921-22.936 12.4 c0.645 0 1.3 0 1.8 0.033c2.871 0 7.316-0.349 7.316-0.349c1.479-0.086 1.7 2.1 0.2 2.3 c0 0-1.487 0.174-3.142 0.261l9.997 29.735l6.008-18.017l-4.276-11.718c-1.479-0.087-2.879-0.261-2.879-0.261 c-1.48-0.087-1.306-2.349 0.174-2.262c0 0 4.5 0.3 7.2 0.349c2.87 0 7.317-0.349 7.317-0.349 c1.479-0.086 1.7 2.1 0.2 2.262c0 0-1.489 0.174-3.142 0.261l9.92 29.508l2.739-9.148 C49.628 35.7 50.5 33 50.5 30.614z M32.481 34.4l-8.237 23.934c2.46 0.7 5.1 1.1 7.8 1.1 c3.197 0 6.262-0.552 9.116-1.556c-0.072-0.118-0.141-0.243-0.196-0.379L32.481 34.4z M56.088 18.8 c0.119 0.9 0.2 1.8 0.2 2.823c0 2.785-0.521 5.916-2.088 9.832l-8.385 24.242c8.161-4.758 13.65-13.6 13.65-23.728 C59.451 27.2 58.2 22.7 56.1 18.83z M32 0c-17.645 0-32 14.355-32 32C0 49.6 14.4 64 32 64s32-14.355 32-32.001 C64 14.4 49.6 0 32 0z M32 62.533c-16.835 0-30.533-13.698-30.533-30.534C1.467 15.2 15.2 1.5 32 1.5 s30.534 13.7 30.5 30.532C62.533 48.8 48.8 62.5 32 62.533z" class="style0"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-144
View File
@@ -1,144 +0,0 @@
<svg width="160" height="160" version="1.1" viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="160" height="160" image-rendering="optimizeQuality" preserveAspectRatio="none" xlink:href="data:image/jpeg;base64,/9j/4QDKRXhpZgAATU0AKgAAAAgABgESAAMAAAABAAEAAAEaAAUAAAABAAAAVgEbAAUAAAABAAAA
XgEoAAMAAAABAAIAAAITAAMAAAABAAEAAIdpAAQAAAABAAAAZgAAAAAAAABIAAAAAQAAAEgAAAAB
AAeQAAAHAAAABDAyMjGRAQAHAAAABAECAwCgAAAHAAAABDAxMDCgAQADAAAAAQABAACgAgAEAAAA
AQAAAUCgAwAEAAAAAQAAAUCkBgADAAAAAQAAAAAAAAAAAAD/2wCEAAEBAQEBAQIBAQIDAgICAwQD
AwMDBAUEBAQEBAUGBQUFBQUFBgYGBgYGBgYHBwcHBwcICAgICAkJCQkJCQkJCQkBAQEBAgICBAIC
BAkGBQYJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCf/d
AAQACv/AABEIAKAAoAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQ
AAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX
GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqS
k5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz
9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQID
EQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RF
RkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqy
s7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/
AP8AP/ooooA734ceM734f+MrLxVZ5b7LIC6D+OI8SJ26r098elft/wCHLyz1fT7fVtMcS21zGssT
r0ZHGVI/CvwEBx0r9R/2J/iMuveH5/hxqT5udL/fWuTy1s5+ZR/1zc/grAdq/LvFHKPaYT63Baw3
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
25fimfNdFFFf3gfxCf/Z
"/>
</svg>

Before

Width:  |  Height:  |  Size: 11 KiB

-20
View File
@@ -1,20 +0,0 @@
"""Load the Nix-generated config for Sovran_SystemsOS_Hub."""
import json
import os
def load_config() -> dict:
"""Read config from the path injected by the Nix derivation."""
path = os.environ.get(
"SOVRAN_HUB_CONFIG",
os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"config.json",
),
)
try:
with open(path, "r") as fh:
return json.load(fh)
except (FileNotFoundError, json.JSONDecodeError):
return {"refresh_interval": 5, "command_method": "systemctl", "services": []}
@@ -1,321 +0,0 @@
#!/usr/bin/env bash
# ── Sovran Hub External Backup Script ────────────────────────────
# Backs up Sovran_SystemsOS data to an external USB hard drive.
# Designed for the Hub web UI (no GUI dependencies).
#
# Your Sovran Pro already backs up your data automatically to its
# internal second drive (BTCEcoandBackup at /run/media/Second_Drive).
# This script creates an additional copy on an external USB drive —
# storing your data in a third location for maximum protection.
#
# Usage:
# BACKUP_TARGET=/run/media/<user>/<drive> bash sovran-hub-backup.sh
# (or run with no env var to auto-detect the first external USB drive)
set -euo pipefail
BACKUP_LOG="/var/log/sovran-hub-backup.log"
BACKUP_STATUS="/var/log/sovran-hub-backup.status"
MEDIA_ROOT="/run/media"
MIN_FREE_GB=10
HUB_CONFIG_JSON="/var/lib/sovran-hub/config.json"
ROLE_STATE_NIX="/etc/nixos/role-state.nix"
# ── Internal drive labels/paths to NEVER use as backup targets ───
INTERNAL_LABELS=("BTCEcoandBackup" "sovran_systemsos")
INTERNAL_MOUNTS=("/run/media/Second_Drive" "/boot/efi" "/")
# ── Logging helpers ──────────────────────────────────────────────
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$msg" | tee -a "$BACKUP_LOG"
}
set_status() {
echo "$1" > "$BACKUP_STATUS"
}
fail() {
log "ERROR: $*"
set_status "FAILED"
exit 1
}
# ── Check whether a mount point is an internal drive ────────────
is_internal() {
local mnt="$1"
# Reject known internal mount points and their subdirectories
for internal in "${INTERNAL_MOUNTS[@]}"; do
if [[ "$mnt" == "$internal" || "$mnt" == "${internal}/"* ]]; then
return 0
fi
done
return 1
}
# ── Use lsblk to find the first genuine external USB drive ───────
find_external_drive() {
local target=""
# lsblk JSON output: NAME,LABEL,MOUNTPOINT,HOTPLUG,RM,TYPE
if command -v lsblk &>/dev/null; then
while IFS=$'\t' read -r dev_type hotplug removable label mountpoint; do
# Must be a partition or disk, and be removable/hotplug
[[ "$dev_type" == "part" || "$dev_type" == "disk" ]] || continue
[[ "$hotplug" == "1" || "$removable" == "1" ]] || continue
[[ -n "$mountpoint" ]] || continue
# Filter out internal labels
local skip=0
for lbl in "${INTERNAL_LABELS[@]}"; do
[[ "$label" == "$lbl" ]] && skip=1 && break
done
[[ "$skip" -eq 1 ]] && continue
# Filter out internal mount points
is_internal "$mountpoint" && continue
if mountpoint -q "$mountpoint" 2>/dev/null; then
target="$mountpoint"
break
fi
done < <(lsblk -J -o NAME,LABEL,MOUNTPOINT,HOTPLUG,RM,TYPE 2>/dev/null | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
def flatten(devs):
for d in devs:
yield d
for c in d.get('children', []):
yield from flatten([c])
for d in flatten(data.get('blockdevices', [])):
print('\t'.join([
d.get('type') or '',
str(d.get('hotplug') or '0'),
str(d.get('rm') or '0'),
d.get('label') or '',
d.get('mountpoint') or '',
]))
" 2>/dev/null || true)
fi
# Fallback: walk /run/media/ if lsblk produced nothing
if [[ -z "$target" && -d "$MEDIA_ROOT" ]]; then
while IFS= read -r -d '' mnt; do
is_internal "$mnt" && continue
# Check label via lsblk on the device backing this mount
local dev
dev=$(findmnt -n -o SOURCE "$mnt" 2>/dev/null || true)
if [[ -n "$dev" ]]; then
local lbl
lbl=$(lsblk -n -o LABEL "$dev" 2>/dev/null || true)
local skip=0
for internal_lbl in "${INTERNAL_LABELS[@]}"; do
[[ "$lbl" == "$internal_lbl" ]] && skip=1 && break
done
[[ "$skip" -eq 1 ]] && continue
fi
if mountpoint -q "$mnt" 2>/dev/null; then
target="$mnt"
break
fi
done < <(find "$MEDIA_ROOT" -mindepth 2 -maxdepth 2 -type d -print0 2>/dev/null)
fi
echo "$target"
}
# ── Detect the configured system role ───────────────────────────
#
# Priority:
# 1. Hub config JSON (/var/lib/sovran-hub/config.json) — "role" key
# 2. role-state.nix (/etc/nixos/role-state.nix) — grep for true flag
# 3. Default: server_plus_desktop
detect_role() {
local role="server_plus_desktop"
# 1. Try the Hub config JSON
if [[ -f "$HUB_CONFIG_JSON" ]] && command -v python3 &>/dev/null; then
local r
r=$(python3 -c \
"import json,sys; d=json.load(open(sys.argv[1])); print(d.get('role',''))" \
"$HUB_CONFIG_JSON" 2>/dev/null || true)
if [[ -n "$r" ]]; then
echo "$r"
return
fi
fi
# 2. Fall back to parsing role-state.nix
if [[ -f "$ROLE_STATE_NIX" ]]; then
if grep -q 'roles\.desktop = lib\.mkDefault true' "$ROLE_STATE_NIX" 2>/dev/null; then
role="desktop"
elif grep -q 'roles\.node = lib\.mkDefault true' "$ROLE_STATE_NIX" 2>/dev/null; then
role="node"
fi
fi
echo "$role"
}
# ── Initialise log file ──────────────────────────────────────────
: > "$BACKUP_LOG"
set_status "RUNNING"
log "=== Sovran_SystemsOS External Hub Backup ==="
log "Starting backup process…"
# ── Detect system role ───────────────────────────────────────────
ROLE="$(detect_role)"
case "$ROLE" in
desktop) ROLE_LABEL="Desktop Only" ;;
node) ROLE_LABEL="Node (Bitcoin-only)" ;;
server_plus_desktop) ROLE_LABEL="Server + Desktop" ;;
*) ROLE_LABEL="$ROLE" ;;
esac
log "Detected role: $ROLE_LABEL"
# ── Detect target drive ──────────────────────────────────────────
if [[ -n "${BACKUP_TARGET:-}" ]]; then
TARGET="$BACKUP_TARGET"
# Safety: never allow internal drives even if explicitly passed
if is_internal "$TARGET"; then
fail "Target '$TARGET' is an internal system drive and cannot be used for external backup."
fi
log "Using specified backup target: $TARGET"
else
log "Auto-detecting external USB drives…"
TARGET="$(find_external_drive)"
if [[ -z "$TARGET" ]]; then
fail "No external USB drive detected. " \
"Please plug in an exFAT-formatted USB drive (≥500 GB) and try again."
fi
log "Detected external drive: $TARGET"
fi
# ── Verify mount point ───────────────────────────────────────────
[[ -d "$TARGET" ]] || fail "Target path '$TARGET' does not exist."
mountpoint -q "$TARGET" || fail "Target path '$TARGET' is not a mount point."
# ── Check free disk space (require ≥ 10 GB) ──────────────────────
FREE_KB=$(df -k --output=avail "$TARGET" | tail -1)
FREE_GB=$(( FREE_KB / 1024 / 1024 ))
log "Free space on drive: ${FREE_GB} GB"
(( FREE_GB >= MIN_FREE_GB )) || \
fail "Not enough free space on drive (${FREE_GB} GB available, ${MIN_FREE_GB} GB required)."
# ── Create timestamped backup directory ─────────────────────────
TIMESTAMP="$(date '+%Y%m%d_%H%M%S')"
BACKUP_DIR="${TARGET}/Sovran_SystemsOS_Backup/${TIMESTAMP}"
mkdir -p "$BACKUP_DIR"
log "Backup destination: $BACKUP_DIR"
# ── Stage 1/4: NixOS configuration ──────────────────────────────
log ""
log "── Stage 1/4: NixOS configuration (/etc/nixos) ──────────────"
if [[ -d /etc/nixos ]]; then
rsync -a --info=progress2 /etc/nixos/ "$BACKUP_DIR/nixos/" 2>&1 | tee -a "$BACKUP_LOG" || \
fail "Stage 1 failed while copying /etc/nixos"
log "Stage 1 complete."
else
log "WARNING: /etc/nixos not found — skipping."
fi
# ── Stage 2/4: Secrets ──────────────────────────────────────────
log ""
log "── Stage 2/4: Secrets ───────────────────────────────────────"
mkdir -p "$BACKUP_DIR/secrets"
if [[ "$ROLE" == "desktop" ]]; then
log "Skipping /etc/nix-bitcoin-secrets — not applicable for Desktop Only role."
else
if [[ -e /etc/nix-bitcoin-secrets ]]; then
rsync -a --info=progress2 /etc/nix-bitcoin-secrets "$BACKUP_DIR/secrets/" 2>&1 | tee -a "$BACKUP_LOG" || \
log "WARNING: Could not copy /etc/nix-bitcoin-secrets — continuing."
else
log " (not found: /etc/nix-bitcoin-secrets — skipping)"
fi
fi
log "Stage 2 complete."
# ── Stage 3/4: Home directory ────────────────────────────────────
log ""
log "── Stage 3/4: Home directory (/home) ───────────────────────"
if [[ -d /home ]]; then
rsync -a --info=progress2 \
--exclude='.cache/' \
--exclude='.local/share/Trash/' \
--exclude='*/Trash/' \
/home/ "$BACKUP_DIR/home/" 2>&1 | tee -a "$BACKUP_LOG" || \
fail "Stage 3 failed while copying /home"
log "Stage 3 complete."
else
log "WARNING: /home not found — skipping."
fi
# ── Stage 4/4: System data ───────────────────────────────────────
log ""
log "── Stage 4/4: System data (/var/lib) ────────────────────────"
if [[ "$ROLE" == "desktop" ]]; then
if [[ -d /var/lib ]]; then
rsync -a --info=progress2 \
--filter='- /lnd/***' \
--exclude='logs/' \
--exclude='log/' \
--exclude='*/logs/' \
--exclude='*/log/' \
/var/lib/ "$BACKUP_DIR/var-lib/" 2>&1 | tee -a "$BACKUP_LOG" || \
fail "Stage 4 failed while copying /var/lib for Desktop Only role"
log "Stage 4 complete (Desktop Only role excludes /var/lib/lnd)."
else
log "WARNING: /var/lib not found — skipping."
fi
elif [[ -d /var/lib ]]; then
rsync -a --info=progress2 \
--exclude='logs/' \
--exclude='log/' \
--exclude='*/logs/' \
--exclude='*/log/' \
/var/lib/ "$BACKUP_DIR/var-lib/" 2>&1 | tee -a "$BACKUP_LOG" || \
fail "Stage 4 failed while copying /var/lib"
log "Stage 4 complete."
else
log "WARNING: /var/lib not found — skipping."
fi
# ── Generate manifest ────────────────────────────────────────────
log ""
log "Generating BACKUP_MANIFEST.txt …"
{
echo "Sovran_SystemsOS Backup Manifest"
echo "Generated: $(date)"
echo "Hostname: $(hostname)"
echo "Role: $ROLE_LABEL"
echo "Target: $TARGET"
echo ""
echo "Contents:"
find "$BACKUP_DIR" -mindepth 1 -maxdepth 2 | sort
} > "$BACKUP_DIR/BACKUP_MANIFEST.txt"
log "Manifest written to $BACKUP_DIR/BACKUP_MANIFEST.txt"
# ── Done ─────────────────────────────────────────────────────────
log ""
log "All Finished! Your data is now backed up to a third location."
log "Please eject the drive safely before removing it from your Sovran Pro."
set_status "SUCCESS"
File diff suppressed because it is too large Load Diff
@@ -1,141 +0,0 @@
/* Sovran_SystemsOS Hub — Web UI Stylesheet
Dark theme — near-black with green accents matching the Sovran Hub icon
v8 — Black-forward, green used for accents/borders/highlights only */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg-color: #080a09;
--surface-color: rgba(14, 16, 15, 0.7);
--card-color: rgba(20, 22, 21, 0.6);
--border-color: rgba(255, 255, 255, 0.06);
--text-primary: #ecf3ef;
--text-secondary: #8aaa9a;
--text-dim: #4a6658;
--accent-color: #5EAD8A;
--green: #6DBF8B;
--yellow: #e5a50a;
--red: #e01b24;
--grey: #5E7A6A;
--radius-card: 18px;
--radius-btn: 8px;
--shadow-card: 0 4px 16px rgba(0, 0, 0, 0.4);
--shadow-hover: 0 8px 32px rgba(0, 0, 0, 0.5);
}
html, body {
height: 100%;
}
body {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background:
radial-gradient(ellipse at top, rgba(94, 173, 138, 0.04) 0%, transparent 50%),
var(--bg-color);
color: var(--text-primary);
line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* ── Login page ──────────────────────────────────────────────────── */
.login-wrapper {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 24px;
}
.login-card {
background-color: rgba(14, 16, 15, 0.75);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 20px;
padding: 48px 40px;
width: 100%;
max-width: 400px;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}
.login-header {
text-align: center;
margin-bottom: 32px;
}
.login-logo {
height: 64px;
margin-bottom: 16px;
}
.login-title {
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
}
.login-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.form-group label {
display: block;
font-size: 0.82rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 6px;
}
.form-group input {
width: 100%;
padding: 10px 14px;
border: 1px solid var(--border-color);
border-radius: var(--radius-btn);
background-color: var(--card-color);
color: var(--text-primary);
font-size: 0.92rem;
}
.form-group input:focus {
outline: none;
border-color: var(--accent-color);
}
.btn-login {
width: 100%;
padding: 12px;
border-radius: var(--radius-btn);
background-color: var(--accent-color);
color: #0A1A10;
font-size: 0.95rem;
font-weight: 700;
margin-top: 8px;
}
.btn-login:hover {
opacity: 0.88;
}
.login-error {
background-color: rgba(224, 27, 36, 0.12);
border: 1px solid var(--red);
color: #f87171;
padding: 10px 14px;
border-radius: 8px;
font-size: 0.85rem;
display: none;
}
.login-error.visible {
display: block;
}
@@ -1,86 +0,0 @@
/* ── Buttons ────────────────────────────────────────────────────── */
button {
font-family: inherit;
cursor: pointer;
border: none;
outline: none;
transition: opacity 0.15s, box-shadow 0.15s, background-color 0.15s;
}
button:disabled {
opacity: 0.45;
cursor: default;
}
.btn {
padding: 7px 16px;
border-radius: var(--radius-btn);
font-size: 0.88rem;
font-weight: 600;
}
.btn-primary {
background-color: var(--accent-color);
color: #0A1A10;
}
.btn-primary:hover:not(:disabled) {
opacity: 0.88;
}
/* Update System button: uses accent green by default */
.btn-update {
background-color: #4A9474;
color: #E0F2EA;
position: relative;
display: flex;
align-items: center;
gap: 8px;
}
.btn-update:hover:not(:disabled) {
opacity: 0.88;
}
/* Update System button: brighter green when updates are available */
.btn-update.has-updates {
background-color: #5EAD8A;
color: #0A1A10;
}
.btn-update.has-updates:hover:not(:disabled) {
background-color: #78C8A2;
}
.update-badge {
display: none;
width: 10px;
height: 10px;
background-color: var(--yellow);
border-radius: 50%;
animation: pulse-badge 1.4s ease-in-out infinite;
}
.update-badge.visible {
display: inline-block;
}
@keyframes pulse-badge {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.35); }
}
.btn-icon {
background: none;
color: var(--text-secondary);
padding: 6px;
border-radius: 50%;
font-size: 1.1rem;
line-height: 1;
}
.btn-icon:hover:not(:disabled) {
background-color: var(--border-color);
color: var(--text-primary);
}
@@ -1,173 +0,0 @@
/* ── Domain setup modal ──────────────────────────────────────────── */
domain-narrow-dialog {
max-width: 500px;
}
domain-field-group {
margin-bottom: 14px;
}
domain-field-label {
display: block;
font-size: 0.82rem;
color: var(--text-secondary);
margin-bottom: 6px;
font-weight: 600;
}
domain-field-input {
width: 100%;
background-color: #0c0f0e;
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 10px 12px;
font-size: 0.9rem;
box-sizing: border-box;
}
domain-field-input:focus {
outline: none;
border-color: var(--accent-color);
}
domain-field-actions {
display: flex;
gap: 10px;
margin-top: 18px;
justify-content: flex-end;
}
/* ── Port Requirements modal ─────────────────────────────────────── */
.domain-narrow-dialog {
max-width: 500px;
}
.domain-setup-intro {
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 16px;
}
.domain-setup-intro ol {
padding-left: 20px;
margin-top: 8px;
}
.domain-setup-intro li {
margin-bottom: 6px;
}
.domain-field-group {
margin-bottom: 14px;
}
.domain-field-label {
display: block;
font-size: 0.82rem;
color: var(--text-secondary);
margin-bottom: 6px;
font-weight: 600;
}
.domain-field-input {
width: 100%;
background-color: #0c0f0e;
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 10px 12px;
font-size: 0.9rem;
box-sizing: border-box;
}
.domain-field-input:focus {
outline: none;
border-color: var(--accent-color);
}
.domain-field-hint {
font-size: 0.75rem;
color: var(--text-dim);
margin-top: 4px;
font-style: italic;
}
.domain-field-actions {
display: flex;
gap: 10px;
margin-top: 18px;
justify-content: flex-end;
}
.port-req-intro {
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 10px;
}
.port-req-hint {
font-size: 0.82rem;
color: var(--text-dim);
line-height: 1.6;
margin-top: 10px;
margin-bottom: 10px;
}
.port-req-internal-ip {
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
font-weight: 700;
color: var(--accent-color);
}
.port-req-table {
width: 100%;
border-collapse: collapse;
font-size: 0.82rem;
margin-top: 8px;
margin-bottom: 8px;
}
.port-req-table th {
text-align: left;
color: var(--text-dim);
font-weight: 600;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 6px 10px;
border-bottom: 1px solid var(--border-color);
}
.port-req-table td {
padding: 8px 10px;
border-bottom: 1px solid rgba(30, 45, 39, 0.5);
color: var(--text-primary);
}
.port-req-table tr:last-child td {
border-bottom: none;
}
.port-req-port {
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
font-weight: 600;
color: var(--accent-color);
}
.port-req-proto {
text-transform: uppercase;
color: var(--text-secondary);
}
.port-req-desc {
color: var(--text-secondary);
}
.port-req-status {
font-weight: 600;
}
@@ -1,143 +0,0 @@
/* ── Feature Manager styles ──────────────────────────────────────── */
.feature-manager-section {
margin-bottom: 32px;
}
.feature-subcategory {
margin-bottom: 16px;
}
.feature-subcategory-header {
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-dim);
margin-bottom: 8px;
padding-left: 4px;
}
.feature-cards-wrap {
display: flex;
flex-direction: column;
gap: 10px;
}
.feature-card {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 14px 16px;
}
.feature-card-top {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 8px;
}
.feature-card-info {
flex: 1;
min-width: 0;
}
.feature-card-name {
font-size: 0.9rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 4px;
}
.feature-card-desc {
font-size: 0.78rem;
color: var(--text-secondary);
line-height: 1.5;
}
.feature-card-status {
font-size: 0.72rem;
color: var(--text-dim);
margin-top: 6px;
}
.feature-toggle {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
flex-shrink: 0;
cursor: pointer;
}
.feature-toggle-input {
opacity: 0;
width: 0;
height: 0;
position: absolute;
}
.feature-toggle-slider {
position: absolute;
inset: 0;
background-color: var(--border-color);
border-radius: 24px;
transition: background-color 0.2s;
}
.feature-toggle-slider::before {
content: "";
position: absolute;
width: 18px;
height: 18px;
left: 3px;
top: 3px;
background-color: #fff;
border-radius: 50%;
transition: transform 0.2s;
}
.feature-toggle.active .feature-toggle-slider {
background-color: var(--green);
}
.feature-toggle.active .feature-toggle-slider::before {
transform: translateX(20px);
}
.feature-domain-badge {
display: flex;
align-items: center;
gap: 6px;
margin-top: 6px;
font-size: 0.78rem;
}
.feature-domain-icon {
flex-shrink: 0;
}
.feature-domain-label {
color: var(--text-secondary);
}
.feature-domain-label--checking {
color: var(--text-dim);
font-style: italic;
}
.feature-domain-label--ok {
color: var(--green);
font-weight: 600;
}
.feature-domain-label--warn {
color: var(--yellow);
font-weight: 600;
}
.feature-domain-label--error {
color: var(--red);
font-weight: 600;
}
@@ -1,93 +0,0 @@
/* ── Header bar ─────────────────────────────────────────────────── */
.header-bar {
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
background-color: rgba(10, 12, 11, 0.82);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
padding: 8px 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 16px;
position: sticky;
top: 0;
z-index: 100;
}
.header-logo {
height: 80px;
width: auto;
display: block;
flex-shrink: 0;
}
.header-bar .title {
font-size: 1.15rem;
font-weight: 700;
color: var(--text-primary);
}
.header-buttons {
display: flex;
align-items: center;
gap: 10px;
}
.role-badge {
background-color: var(--accent-color);
color: #0A1A10;
font-size: 0.72rem;
font-weight: 700;
padding: 3px 10px;
border-radius: 20px;
letter-spacing: 0.03em;
}
/* ── IP bar ─────────────────────────────────────────────────────── */
.ip-bar {
background-color: rgba(10, 12, 11, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
padding: 8px 24px;
display: flex;
align-items: center;
justify-content: center;
gap: 32px;
font-size: 0.82rem;
color: var(--text-secondary);
}
.ip-bar .ip-label {
color: var(--text-dim);
margin-right: 6px;
}
.ip-bar .ip-value {
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
color: var(--accent-color);
font-weight: 600;
}
.ip-separator {
color: var(--border-color);
}
.btn-logout {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.18);
color: var(--text-secondary);
font-size: 0.78rem;
font-weight: 600;
padding: 4px 12px;
border-radius: var(--radius-btn);
cursor: pointer;
transition: border-color 0.15s, color 0.15s;
}
.btn-logout:hover {
border-color: var(--accent-color);
color: var(--accent-color);
}
@@ -1,175 +0,0 @@
/* ── Main content ───────────────────────────────────────────────── */
.main-content {
display: flex;
align-items: flex-start;
flex: 1;
overflow: hidden;
max-width: 1400px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
/* ── Sidebar ────────────────────────────────────────────────────── */
.sidebar {
width: 270px;
flex-shrink: 0;
height: 100%;
overflow-y: auto;
border-right: 1px solid rgba(255, 255, 255, 0.06);
background-color: rgba(12, 14, 13, 0.65);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
padding: 20px 14px;
display: flex;
flex-direction: column;
gap: 0;
}
/* ── Sidebar: Tech Support button ───────────────────────────────── */
.sidebar-support-btn {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 12px 14px;
color: var(--text-primary);
cursor: pointer;
transition: border-style 0.15s, border-color 0.15s, background-color 0.15s;
text-align: left;
}
.sidebar-support-btn:hover {
border-color: var(--accent-color);
border-style: solid;
background-color: #162320;
}
.sidebar-support-btn + .sidebar-support-btn {
margin-top: 8px;
}
.sidebar-support-icon {
font-size: 1.5rem;
flex-shrink: 0;
}
.sidebar-support-text {
display: flex;
flex-direction: column;
gap: 2px;
}
.sidebar-support-title {
font-size: 0.88rem;
font-weight: 700;
color: var(--text-primary);
}
.sidebar-support-hint {
font-size: 0.72rem;
color: var(--accent-color);
font-weight: 600;
}
.sidebar-divider {
border: none;
border-top: 1px solid var(--border-color);
margin: 16px 0;
}
/* ── Upgrade modal ──────────────────────────────────────────────── */
.upgrade-dialog {
max-width: 480px;
}
.upgrade-info-box {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 14px 18px;
margin-bottom: 14px;
}
.upgrade-info-title {
font-size: 0.88rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
}
.upgrade-info-list {
padding-left: 20px;
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.7;
margin: 0;
}
.upgrade-info-list a {
color: var(--accent-color);
}
.upgrade-rebuild-note {
font-style: italic;
color: var(--text-dim);
font-size: 0.82rem;
}
/* ── Tiles area ─────────────────────────────────────────────────── */
#tiles-area {
flex: 1;
height: 100%;
overflow-y: auto;
padding: 24px 20px 48px;
min-width: 0;
}
/* ── Category sections ──────────────────────────────────────────── */
.category-section {
margin-bottom: 32px;
}
.section-header {
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-secondary);
margin-bottom: 4px;
padding-left: 4px;
}
.section-divider {
border: none;
border-top: 1px solid var(--border-color);
margin-bottom: 16px;
}
.tiles-grid {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
/* ── Empty state ────────────────────────────────────────────────── */
.empty-state {
text-align: center;
padding: 64px 24px;
color: var(--text-dim);
}
.empty-state p {
font-size: 1rem;
margin-bottom: 8px;
}
@@ -1,438 +0,0 @@
/* ── Update modal ────────────────────────────────────────────────── */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
z-index: 200;
align-items: center;
justify-content: center;
}
.modal-overlay.open {
display: flex;
}
.modal-dialog {
background-color: rgba(14, 16, 15, 0.8);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
width: 90vw;
max-width: 900px;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
}
.modal-header {
display: flex;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
gap: 12px;
}
.modal-title {
font-size: 1rem;
font-weight: 700;
flex: 1;
}
.modal-status {
font-size: 0.85rem;
color: var(--text-secondary);
}
.modal-spinner {
width: 18px;
height: 18px;
border: 2.5px solid var(--border-color);
border-top-color: var(--accent-color);
border-radius: 50%;
animation: spin 0.75s linear infinite;
display: none;
}
.modal-spinner.spinning {
display: block;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.modal-log {
flex: 1;
overflow-y: auto;
padding: 12px 16px;
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
font-size: 0.78rem;
line-height: 1.6;
color: var(--text-primary);
background-color: #0c0f0e;
white-space: pre-wrap;
word-break: break-all;
min-height: 200px;
}
.modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
padding: 12px 20px;
border-top: 1px solid var(--border-color);
}
/* Reboot = GREEN */
.modal-footer .btn-reboot,
button.btn-reboot {
background-color: #6DBF8B;
color: #fff;
}
.modal-footer .btn-reboot:hover:not(:disabled),
button.btn-reboot:hover:not(:disabled) {
background-color: #529E7E;
}
.btn-save {
background-color: var(--yellow);
color: #0A1A10;
}
.btn-save:hover:not(:disabled) {
background-color: #c98d08;
}
.btn-close-modal {
background-color: var(--border-color);
color: var(--text-primary);
}
.btn-close-modal:hover:not(:disabled) {
background-color: #1c2a24;
}
/* ── Credentials info modal ──────────────────────────────────────── */
.creds-dialog {
background-color: rgba(14, 16, 15, 0.8);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
width: 90vw;
max-width: 700px;
max-height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
animation: creds-fade-in 0.2s ease-out;
}
@keyframes creds-fade-in {
from { opacity: 0; transform: scale(0.95) translateY(8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.creds-header {
display: flex;
align-items: center;
padding: 20px 28px;
border-bottom: 1px solid var(--border-color);
}
.creds-title {
font-size: 1.15rem;
font-weight: 700;
flex: 1;
display: flex;
align-items: center;
gap: 10px;
}
.creds-title-icon {
width: 28px;
height: 28px;
vertical-align: middle;
border-radius: 6px;
flex-shrink: 0;
}
.creds-close-btn {
background: none;
color: var(--text-secondary);
font-size: 1.3rem;
padding: 4px 8px;
border-radius: 6px;
cursor: pointer;
border: none;
}
.creds-close-btn:hover {
background-color: var(--border-color);
color: var(--text-primary);
}
.creds-body {
padding: 24px 28px;
overflow-y: auto;
}
.creds-loading {
color: var(--text-dim);
text-align: center;
padding: 24px 0;
}
.creds-row {
margin-bottom: 20px;
}
.creds-row:last-child {
margin-bottom: 0;
}
.creds-label {
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-dim);
margin-bottom: 6px;
}
.creds-value-wrap {
display: flex;
align-items: flex-start;
gap: 10px;
}
.creds-value {
flex: 1;
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
font-size: 0.92rem;
color: var(--text-primary);
background-color: #0c0f0e;
padding: 12px 16px;
border-radius: 8px;
word-break: break-all;
white-space: pre-wrap;
line-height: 1.6;
border: 1px solid var(--border-color);
}
.creds-copy-btn {
background-color: var(--border-color);
color: var(--text-primary);
font-size: 0.78rem;
font-weight: 600;
padding: 8px 14px;
border-radius: 6px;
cursor: pointer;
border: none;
white-space: nowrap;
flex-shrink: 0;
align-self: flex-start;
margin-top: 10px;
}
.creds-copy-btn:hover {
background-color: #1c2a24;
}
.creds-copy-btn.copied {
background-color: var(--green);
color: #fff;
}
.creds-empty {
color: var(--text-dim);
text-align: center;
padding: 24px 0;
font-size: 0.88rem;
}
/* ── Credential links ────────────────────────────────────────────── */
.creds-link {
color: #b8f0c0;
text-decoration: none;
word-break: break-all;
}
.creds-link:hover {
text-decoration: underline;
color: #defce6;
}
/* ── Matrix action buttons ───────────────────────────────────────── */
.matrix-actions-divider {
border: none;
border-top: 1px solid var(--border-color);
margin: 18px 0 14px;
}
.matrix-actions-row {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.matrix-action-btn {
background-color: var(--accent-color);
color: #0A1A10;
font-size: 0.88rem;
font-weight: 700;
padding: 10px 18px;
border-radius: 8px;
border: none;
cursor: pointer;
flex: 1;
min-width: 140px;
}
.matrix-action-btn:hover {
background-color: #7CC4A0;
}
.matrix-form-group {
margin-bottom: 14px;
}
.matrix-form-label {
display: block;
font-size: 0.82rem;
color: var(--text-secondary);
margin-bottom: 6px;
font-weight: 600;
}
.matrix-form-input {
width: 100%;
background-color: #0c0f0e;
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 10px 12px;
font-size: 0.9rem;
box-sizing: border-box;
}
.matrix-form-input:focus {
outline: none;
border-color: var(--accent-color);
}
.matrix-form-checkbox-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 14px;
}
.matrix-form-checkbox-row input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--accent-color);
}
.matrix-form-actions {
display: flex;
gap: 10px;
margin-top: 18px;
}
.matrix-form-submit {
background-color: var(--accent-color);
color: #0A1A10;
font-size: 0.88rem;
font-weight: 700;
padding: 10px 20px;
border-radius: 8px;
border: none;
cursor: pointer;
flex: 1;
}
.matrix-form-submit:hover:not(:disabled) {
background-color: #7CC4A0;
}
.matrix-form-submit:disabled {
opacity: 0.6;
cursor: default;
}
.matrix-form-back {
background-color: var(--border-color);
color: var(--text-primary);
font-size: 0.88rem;
font-weight: 600;
padding: 10px 20px;
border-radius: 8px;
border: none;
cursor: pointer;
}
.matrix-form-back:hover {
background-color: #1c2a24;
}
.matrix-form-result {
margin-top: 14px;
padding: 12px 16px;
border-radius: 8px;
font-size: 0.88rem;
line-height: 1.5;
display: none;
}
.matrix-form-result.success {
background-color: rgba(109, 191, 139, 0.12);
border: 1px solid var(--green);
color: var(--green);
display: block;
}
.matrix-form-result.error {
background-color: rgba(239, 68, 68, 0.12);
border: 1px solid #ef4444;
color: #f87171;
display: block;
}
/* ── QR code in credentials modal ────────────────────────────────── */
.creds-qr-wrap {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
margin-bottom: 10px;
}
.creds-qr-img {
width: 240px;
height: 240px;
border-radius: 12px;
border: 4px solid #fff;
background-color: #fff;
image-rendering: pixelated;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
}
.creds-qr-hint {
margin-top: 10px;
font-size: 0.82rem;
color: var(--text-secondary);
font-style: italic;
}
@@ -1,879 +0,0 @@
/* ── Onboarding wizard ──────────────────────────────────────────── */
.onboarding-body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
min-height: 100vh;
background:
radial-gradient(ellipse at top, rgba(94, 173, 138, 0.04) 0%, transparent 50%),
var(--bg-color);
padding: 24px 16px 48px;
overflow-y: auto;
}
.onboarding-shell {
width: 100%;
max-width: 680px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 0;
}
/* Progress bar */
.onboarding-progress-bar {
width: 100%;
height: 4px;
background-color: var(--card-color);
border-radius: 2px;
overflow: hidden;
margin-bottom: 24px;
}
.onboarding-progress-fill {
height: 100%;
background-color: var(--accent-color);
border-radius: 2px;
transition: width 0.4s ease;
width: 20%;
}
/* Step indicator dots */
.onboarding-steps-nav {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
margin-bottom: 28px;
}
.onboarding-step-dot {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: var(--card-color);
border: 2px solid var(--border-color);
color: var(--text-dim);
font-size: 0.78rem;
font-weight: 700;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background-color 0.2s, border-color 0.2s, color 0.2s;
}
.onboarding-step-dot.active {
background-color: var(--accent-color);
border-color: var(--accent-color);
color: #0A1A10;
}
.onboarding-step-dot.completed {
background-color: var(--green);
border-color: var(--green);
color: #0A1A10;
}
.onboarding-step-connector {
flex: 1;
height: 2px;
background-color: var(--border-color);
min-width: 24px;
max-width: 80px;
transition: background-color 0.2s;
}
/* Panel wrapper and panels */
.onboarding-panel-wrap {
position: relative;
}
.onboarding-panel {
display: flex;
flex-direction: column;
gap: 20px;
animation: panel-fade-in 0.25s ease-out;
}
@keyframes panel-fade-in {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Hero / welcome section */
.onboarding-hero {
text-align: center;
padding: 16px 0 4px;
}
.onboarding-logo {
margin-bottom: 16px;
font-size: 3rem;
}
.onboarding-logo-img {
height: 90px;
width: auto;
}
.onboarding-title {
font-size: 1.6rem;
font-weight: 800;
color: var(--text-primary);
margin-bottom: 8px;
line-height: 1.2;
}
.onboarding-subtitle {
font-size: 1rem;
color: var(--text-secondary);
}
/* Cards */
.onboarding-card {
background-color: rgba(14, 16, 15, 0.65);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: var(--radius-card);
padding: 24px 28px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Body text */
.onboarding-body-text {
font-size: 0.92rem;
color: var(--text-secondary);
line-height: 1.65;
}
.onboarding-body-text--dim {
color: var(--text-dim);
font-size: 0.85rem;
}
/* Role row */
.onboarding-role-row {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background-color: var(--card-color);
border-radius: 8px;
}
.onboarding-role-label {
font-size: 0.82rem;
font-weight: 600;
color: var(--text-secondary);
}
.onboarding-role-badge {
font-size: 0.82rem;
font-weight: 700;
color: var(--accent-color);
background-color: rgba(94, 173, 138, 0.10);
padding: 3px 10px;
border-radius: 20px;
border: 1px solid rgba(94, 173, 138, 0.25);
}
/* Step header */
.onboarding-step-header {
padding-bottom: 4px;
}
.onboarding-step-icon {
font-size: 2rem;
display: block;
margin-bottom: 8px;
}
.onboarding-step-title {
font-size: 1.35rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
}
.onboarding-step-desc {
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.6;
}
/* Footer / navigation */
.onboarding-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 24px;
padding-bottom: 24px;
margin-top: auto;
}
.onboarding-btn-next {
/* inherits from .btn.btn-primary */
}
.onboarding-btn-back {
/* inherits from .btn.btn-close-modal */
}
/* Domain configuration (Step 2) */
.onboarding-domain-group {
display: flex;
flex-direction: column;
gap: 6px;
padding: 14px 0;
border-bottom: 1px solid var(--border-color);
}
.onboarding-domain-group:last-child {
border-bottom: none;
}
.onboarding-domain-group--email {
/* email-specific variant */
}
.onboarding-domain-label {
font-size: 0.88rem;
font-weight: 600;
color: var(--text-primary);
}
.onboarding-domain-label--sub {
font-size: 0.78rem;
font-weight: 400;
color: var(--text-dim);
}
.onboarding-domain-input,
.domain-field-input {
width: 100%;
padding: 9px 12px;
border: 1px solid var(--border-color);
border-radius: var(--radius-btn);
background-color: var(--card-color);
color: var(--text-primary);
font-size: 0.88rem;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
transition: border-color 0.15s;
}
.onboarding-domain-input:focus,
.domain-field-input:focus {
outline: none;
border-color: var(--accent-color);
}
.onboarding-hint {
font-size: 0.78rem;
color: var(--text-dim);
line-height: 1.5;
}
.onboarding-hint--inline {
display: inline;
}
/* Port forwarding (Step 3) */
.onboarding-port-note {
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.6;
}
.onboarding-port-ip {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background-color: var(--card-color);
border-radius: 8px;
border: 1px solid var(--border-color);
}
.onboarding-port-ip-label {
font-size: 0.82rem;
font-weight: 600;
color: var(--text-secondary);
white-space: nowrap;
}
.onboarding-port-section {
display: flex;
flex-direction: column;
gap: 10px;
}
.onboarding-port-section-title {
font-size: 0.88rem;
font-weight: 700;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.onboarding-port-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.onboarding-port-table thead th {
text-align: left;
font-size: 0.78rem;
font-weight: 600;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 6px 8px;
border-bottom: 1px solid var(--border-color);
}
.onboarding-port-table td {
padding: 8px 8px;
border-bottom: 1px solid rgba(30, 45, 39, 0.5);
color: var(--text-secondary);
vertical-align: middle;
}
.onboarding-port-table tr:last-child td {
border-bottom: none;
}
.onboarding-port-totals {
padding: 10px 14px;
background-color: var(--card-color);
border-radius: 8px;
border: 1px solid var(--border-color);
font-size: 0.85rem;
color: var(--text-secondary);
}
.onboarding-port-warn {
padding: 10px 14px;
background-color: rgba(229, 165, 10, 0.1);
border: 1px solid rgba(229, 165, 10, 0.35);
border-radius: 8px;
font-size: 0.85rem;
color: var(--yellow);
line-height: 1.5;
}
.onboarding-port-details {
font-size: 0.85rem;
}
.onboarding-port-details-summary {
cursor: pointer;
font-size: 0.82rem;
font-weight: 600;
color: var(--accent-color);
list-style: none;
user-select: none;
}
.onboarding-port-details-summary::-webkit-details-marker {
display: none;
}
.onboarding-port-details-summary::before {
content: '▶ ';
font-size: 0.65em;
}
.onboarding-port-details[open] .onboarding-port-details-summary::before {
content: '▼ ';
}
/* Credentials (Step 4) */
.onboarding-creds-notice {
padding: 12px 16px;
background-color: rgba(94, 173, 138, 0.08);
border: 1px solid rgba(94, 173, 138, 0.20);
border-radius: 8px;
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.55;
}
.onboarding-creds-category {
display: flex;
flex-direction: column;
gap: 8px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border-color);
}
.onboarding-creds-category:last-child {
border-bottom: none;
padding-bottom: 0;
}
.onboarding-creds-category-title {
font-size: 0.78rem;
font-weight: 700;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 2px;
}
.onboarding-creds-service {
background-color: var(--card-color);
border-radius: 8px;
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 8px;
}
.onboarding-creds-service-name {
font-size: 0.88rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 2px;
}
.onboarding-cred-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.82rem;
}
.onboarding-cred-label {
color: var(--text-dim);
font-size: 0.78rem;
min-width: 80px;
flex-shrink: 0;
}
.onboarding-cred-value {
color: var(--text-primary);
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.82rem;
word-break: break-all;
flex: 1;
}
.onboarding-cred-secret {
display: flex;
align-items: center;
gap: 6px;
flex: 1;
}
.onboarding-cred-hidden {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.82rem;
color: var(--text-dim);
letter-spacing: 0.1em;
}
.onboarding-cred-real {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.82rem;
color: var(--text-primary);
word-break: break-all;
display: none;
}
.onboarding-cred-reveal-btn {
font-size: 0.72rem;
padding: 2px 8px;
border-radius: 4px;
background-color: rgba(94, 173, 138, 0.08);
border: 1px solid rgba(94, 173, 138, 0.20);
color: var(--accent-color);
cursor: pointer;
white-space: nowrap;
transition: background-color 0.15s;
}
.onboarding-cred-reveal-btn:hover {
background-color: rgba(94, 173, 138, 0.15);
}
/* Completion checklist (Step 5) */
.onboarding-checklist {
list-style: none;
display: flex;
flex-direction: column;
gap: 10px;
}
.onboarding-checklist li {
font-size: 0.92rem;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 10px;
}
/* Status & feedback */
.onboarding-loading {
font-size: 0.88rem;
color: var(--text-dim);
text-align: center;
padding: 16px 0;
font-style: italic;
}
.onboarding-error {
font-size: 0.85rem;
color: var(--red);
padding: 10px 14px;
background-color: rgba(224, 27, 36, 0.1);
border: 1px solid rgba(224, 27, 36, 0.3);
border-radius: 8px;
line-height: 1.5;
}
.onboarding-save-status {
font-size: 0.85rem;
min-height: 1.2em;
transition: color 0.2s;
}
.onboarding-save-status--ok {
color: var(--green);
}
.onboarding-save-status--error {
color: var(--red);
}
.onboarding-save-status--info {
color: var(--text-secondary);
}
/* ── Timezone / Locale step (Step 2) ────────────────────────────── */
.onboarding-tz-group {
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px 0;
}
.onboarding-tz-search {
width: 100%;
padding: 9px 12px;
border: 1px solid var(--border-color);
border-radius: var(--radius-btn);
background-color: var(--card-color);
color: var(--text-primary);
font-size: 0.88rem;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
transition: border-color 0.15s;
}
.onboarding-tz-search:focus {
outline: none;
border-color: var(--accent-color);
}
.onboarding-tz-select {
width: 100%;
padding: 6px 10px;
border: 1px solid var(--border-color);
border-radius: var(--radius-btn);
background-color: var(--card-color);
color: var(--text-primary);
font-size: 0.88rem;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
transition: border-color 0.15s;
overflow-y: auto;
}
.onboarding-tz-select:focus {
outline: none;
border-color: var(--accent-color);
}
.onboarding-tz-select option {
padding: 4px 8px;
background-color: var(--card-color);
color: var(--text-primary);
}
.onboarding-locale-select {
height: auto;
size: auto;
}
/* ── Password step (Step 3) ─────────────────────────────────────── */
.onboarding-password-group {
display: flex;
flex-direction: column;
gap: 6px;
padding: 10px 0;
}
.onboarding-password-input-wrap {
display: flex;
align-items: center;
gap: 6px;
}
.onboarding-password-input {
flex: 1;
padding: 9px 12px;
border: 1px solid var(--border-color);
border-radius: var(--radius-btn);
background-color: var(--card-color);
color: var(--text-primary);
font-size: 0.88rem;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
transition: border-color 0.15s;
}
.onboarding-password-input:focus {
outline: none;
border-color: var(--accent-color);
}
.onboarding-password-toggle {
padding: 6px 10px;
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: var(--radius-btn);
color: var(--text-secondary);
cursor: pointer;
font-size: 1rem;
line-height: 1;
transition: background-color 0.15s, border-color 0.15s;
flex-shrink: 0;
}
.onboarding-password-toggle:hover {
background-color: rgba(94, 173, 138, 0.10);
border-color: var(--accent-color);
}
.onboarding-password-hint {
font-size: 0.78rem;
color: var(--text-dim);
line-height: 1.4;
}
.onboarding-password-warning {
padding: 10px 14px;
background-color: rgba(229, 165, 10, 0.1);
border: 1px solid rgba(229, 165, 10, 0.35);
border-radius: 8px;
font-size: 0.85rem;
color: var(--yellow);
line-height: 1.5;
margin-top: 6px;
}
.onboarding-password-success {
padding: 12px 16px;
background-color: rgba(94, 173, 138, 0.10);
border: 1px solid rgba(94, 173, 138, 0.30);
border-radius: 8px;
font-size: 0.92rem;
color: var(--green);
line-height: 1.5;
}
.onboarding-password-optional {
margin-top: 12px;
font-size: 0.88rem;
}
.onboarding-password-optional > summary {
cursor: pointer;
font-size: 0.85rem;
font-weight: 600;
color: var(--accent-color);
list-style: none;
user-select: none;
}
.onboarding-password-optional > summary::-webkit-details-marker {
display: none;
}
.onboarding-password-optional > summary::before {
content: '▶ ';
font-size: 0.65em;
}
.onboarding-password-optional[open] > summary::before {
content: '▼ ';
}
/* ── Reboot overlay ─────────────────────────────────────────────── */
.reboot-overlay {
display: none;
position: fixed;
inset: 0;
background-color: rgba(6, 8, 7, 0.7);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 999;
align-items: center;
justify-content: center;
}
.reboot-overlay.visible {
display: flex;
}
.reboot-card {
background-color: rgba(14, 16, 15, 0.8);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 20px;
padding: 48px 56px;
text-align: center;
max-width: 480px;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.8);
animation: reboot-fade-in 0.4s ease-out;
}
@keyframes reboot-fade-in {
from { opacity: 0; transform: scale(0.92) translateY(12px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.reboot-icon {
font-size: 3rem;
color: var(--accent-color);
margin-bottom: 16px;
animation: reboot-spin 2s linear infinite;
display: inline-block;
}
@keyframes reboot-spin {
to { transform: rotate(360deg); }
}
.reboot-title {
font-size: 1.35rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 12px;
}
.reboot-message {
font-size: 0.92rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 24px;
}
.reboot-dots {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 16px;
}
.reboot-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--accent-color);
animation: reboot-bounce 1.4s ease-in-out infinite;
}
.reboot-dot:nth-child(2) { animation-delay: 0.2s; }
.reboot-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes reboot-bounce {
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
40% { opacity: 1; transform: scale(1.2); }
}
.reboot-submessage {
font-size: 0.82rem;
color: var(--text-dim);
font-style: italic;
}
/* ── Responsive ─────────────────────────────────────────────────── */
@media (max-width: 768px) {
body {
overflow: auto;
}
.main-content {
flex-direction: column;
overflow: visible;
}
.sidebar {
width: 100%;
height: auto;
border-right: none;
border-bottom: 1px solid var(--border-color);
padding: 14px 12px;
}
#tiles-area {
height: auto;
overflow-y: visible;
padding: 16px 12px 40px;
}
}
@media (max-width: 600px) {
.header-bar {
padding: 10px 14px;
gap: 10px;
}
.header-bar .title {
font-size: 0.95rem;
}
.ip-bar {
gap: 16px;
flex-wrap: wrap;
padding: 8px 14px;
}
.tiles-grid {
justify-content: center;
}
.service-tile {
width: 140px;
min-height: 130px;
}
.reboot-card {
padding: 36px 28px;
margin: 0 16px;
}
.creds-dialog {
margin: 0 12px;
}
.creds-qr-img {
width: 200px;
height: 200px;
}
}
@@ -1,457 +0,0 @@
/* ── Security Modal sections ──────────────────────────────────────── */
.security-section {
padding: 0 0 8px 0;
}
.security-section-title {
font-size: 1rem;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 10px 0;
}
.security-section-desc {
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 10px;
}
.security-divider {
border: none;
border-top: 1px solid var(--border-color);
margin: 18px 0;
}
/* ── Security Reset warning box ──────────────────────────────────── */
.security-warning-box {
background-color: rgba(180, 40, 40, 0.10);
border-left: 3px solid #c94040;
border-radius: 6px;
padding: 12px 14px;
margin-bottom: 14px;
}
.security-warning-text {
font-size: 0.85rem;
color: var(--text-primary);
margin: 0 0 8px 0;
line-height: 1.5;
}
.security-warning-list {
margin: 6px 0 6px 20px;
padding: 0;
font-size: 0.82rem;
color: var(--text-secondary);
line-height: 1.7;
}
.security-erase-group {
margin-bottom: 14px;
}
.security-erase-label {
display: block;
font-size: 0.85rem;
color: var(--text-primary);
margin-bottom: 6px;
}
.security-erase-input {
width: 100%;
padding: 8px 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--input-bg, var(--card-color));
color: var(--text-primary);
font-size: 0.9rem;
box-sizing: border-box;
}
.security-reset-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn-danger {
background-color: #c94040;
color: #fff;
border: none;
border-radius: 6px;
padding: 8px 18px;
font-size: 0.88rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.15s;
}
.btn-danger:hover:not(:disabled) {
background-color: #a83030;
}
.btn-danger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.security-status-msg {
font-size: 0.83rem;
margin-top: 10px;
min-height: 1.2em;
}
.security-status-info { color: var(--text-secondary); }
.security-status-ok { color: #6DBF8B; }
.security-status-error { color: #e05252; }
/* ── Verify System Integrity ─────────────────────────────────────── */
.security-verify-list {
margin: 8px 0 10px 20px;
padding: 0;
font-size: 0.84rem;
color: var(--text-secondary);
line-height: 1.7;
}
.security-verify-loading {
font-size: 0.85rem;
color: var(--text-secondary);
}
.security-verify-result-card {
background: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.security-verify-row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.security-verify-label {
font-size: 0.82rem;
color: var(--text-secondary);
min-width: 130px;
font-weight: 600;
}
.security-verify-value {
font-size: 0.82rem;
color: var(--text-primary);
}
.security-verify-mono {
font-family: monospace;
font-size: 0.78rem;
word-break: break-all;
}
.security-verify-link {
font-size: 0.78rem;
color: var(--accent-color, #6DBF8B);
text-decoration: none;
}
.security-verify-link:hover { text-decoration: underline; }
.security-verify-badge {
font-size: 0.82rem;
font-weight: 700;
padding: 2px 10px;
border-radius: 12px;
}
.security-verify-pass {
background-color: rgba(109, 191, 139, 0.15);
color: #6DBF8B;
}
.security-verify-fail {
background-color: rgba(224, 82, 82, 0.15);
color: #e05252;
}
.security-verify-errors {
font-size: 0.8rem;
color: var(--text-secondary);
}
.security-verify-pre {
white-space: pre-wrap;
word-break: break-all;
font-size: 0.75rem;
background: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 8px;
margin-top: 6px;
max-height: 150px;
overflow-y: auto;
}
.security-verify-path-row {
display: flex;
gap: 8px;
font-size: 0.78rem;
align-items: flex-start;
flex-wrap: wrap;
}
.security-verify-path-label {
font-weight: 600;
color: var(--text-secondary);
min-width: 70px;
}
/* ── Security Reset full-screen overlay ──────────────────────────── */
.security-reset-overlay {
display: none;
position: fixed;
inset: 0;
background-color: rgba(6, 8, 7, 0.94);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 1000;
align-items: center;
justify-content: center;
animation: security-reset-fade-in 0.35s ease-out;
}
.security-reset-overlay.visible {
display: flex;
}
@keyframes security-reset-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.security-reset-overlay-icon {
font-size: 3rem;
margin-bottom: 16px;
}
/* ── Phase 2: password display box ──────────────────────────────── */
.security-reset-password-label {
font-size: 0.88rem;
color: var(--text-secondary);
margin: 16px 0 8px 0;
}
.security-reset-password-box {
font-family: monospace;
font-size: 1.35rem;
font-weight: 700;
color: var(--text-primary);
background: rgba(109, 191, 139, 0.10);
border: 1.5px solid rgba(109, 191, 139, 0.35);
border-radius: 8px;
padding: 14px 24px;
letter-spacing: 0.04em;
text-align: center;
word-break: break-all;
margin-bottom: 16px;
min-width: 260px;
}
.security-reset-password-warning {
font-size: 0.84rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 20px;
text-align: center;
}
.security-reset-reboot-btn {
background-color: #6DBF8B;
color: #0a0c0b;
border: none;
border-radius: 7px;
padding: 11px 22px;
font-size: 0.88rem;
font-weight: 700;
cursor: pointer;
transition: background-color 0.15s, opacity 0.15s;
white-space: nowrap;
}
.security-reset-reboot-btn:hover:not(:disabled) {
background-color: #5aab78;
}
.security-reset-reboot-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ── First-login security banner ─────────────────────────────────── */
.security-first-login-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 18px;
background-color: rgba(94, 173, 138, 0.08);
border-bottom: 2px solid rgba(94, 173, 138, 0.25);
color: var(--text-primary);
}
.security-banner-content {
display: flex;
align-items: flex-start;
gap: 10px;
flex: 1;
min-width: 0;
}
.security-banner-icon {
font-size: 1.2rem;
flex-shrink: 0;
margin-top: 1px;
}
.security-banner-text {
font-size: 0.85rem;
line-height: 1.5;
color: var(--text-primary);
}
.security-banner-dismiss {
background: none;
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
color: var(--text-secondary);
padding: 2px 7px;
flex-shrink: 0;
line-height: 1.4;
transition: background-color 0.15s;
}
.security-banner-dismiss:hover {
background-color: rgba(0,0,0,0.08);
}
/* ── Legacy security inline warning banner ───────────────────────── */
.security-inline-banner {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px 14px;
margin-bottom: 12px;
background-color: rgba(180, 100, 0, 0.12);
border-left: 3px solid #c97a00;
border-radius: 6px;
color: var(--text-primary);
}
.security-inline-icon {
font-size: 1rem;
color: #e69000;
flex-shrink: 0;
}
.security-inline-text {
font-size: 0.82rem;
line-height: 1.5;
color: var(--text-secondary);
}
.security-inline-link {
display: inline-block;
font-size: 0.82rem;
font-weight: 600;
color: #e69000;
text-decoration: none;
border: 1px solid #c97a00;
border-radius: 4px;
padding: 4px 10px;
align-self: flex-start;
transition: background-color 0.15s;
}
.security-inline-link:hover {
background-color: rgba(180, 100, 0, 0.22);
}
/* ── System change-password form extras ──────────────────────────── */
.sys-chpw-header {
margin-bottom: 14px;
}
.sys-chpw-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
}
.sys-chpw-desc {
font-size: 0.82rem;
color: var(--text-secondary);
line-height: 1.5;
}
.pw-input-wrap {
position: relative;
display: flex;
align-items: center;
}
.pw-input-wrap .matrix-form-input {
padding-right: 2.4rem;
width: 100%;
}
.pw-toggle-btn {
position: absolute;
right: 6px;
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
padding: 2px 4px;
line-height: 1;
color: var(--text-secondary);
opacity: 0.75;
transition: opacity 0.15s;
}
.pw-toggle-btn:hover {
opacity: 1;
}
.pw-hint {
font-size: 0.76rem;
color: var(--text-secondary);
margin-top: 4px;
}
.pw-credentials-note {
font-size: 0.78rem;
color: #c97a00;
background-color: rgba(180, 100, 0, 0.10);
border-left: 2px solid #c97a00;
border-radius: 4px;
padding: 7px 10px;
margin-bottom: 12px;
line-height: 1.5;
}
@@ -1,376 +0,0 @@
/* ── Tech Support modal ──────────────────────────────────────────── */
.support-section {
text-align: center;
}
.support-icon-big {
font-size: 3rem;
margin-bottom: 12px;
}
.support-active-icon {
animation: none;
}
.support-heading {
font-size: 1.15rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
}
.support-active-heading {
color: var(--green);
}
.support-desc {
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 16px;
text-align: left;
}
.support-active-note {
font-size: 0.88rem;
color: var(--text-secondary);
margin-bottom: 16px;
}
.support-info-box {
background-color: var(--card-color);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 14px 18px;
margin-bottom: 16px;
text-align: left;
}
.support-active-box {
border-color: var(--green);
}
.support-info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 0;
}
.support-info-label {
font-size: 0.82rem;
color: var(--text-dim);
font-weight: 600;
}
.support-info-value {
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
font-size: 0.88rem;
color: var(--accent-color);
font-weight: 600;
}
.support-info-hint {
font-size: 0.72rem;
color: var(--text-dim);
margin-top: 6px;
font-style: italic;
}
.support-steps {
text-align: left;
margin-bottom: 16px;
padding: 14px 18px;
background-color: var(--card-color);
border-radius: 10px;
border: 1px solid var(--border-color);
}
.support-steps-title {
font-size: 0.82rem;
font-weight: 700;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 8px;
}
.support-steps ol {
padding-left: 20px;
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.7;
}
.support-steps code {
background-color: rgba(94, 173, 138, 0.10);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.82rem;
color: var(--accent-color);
}
.support-btn-enable {
width: 100%;
padding: 12px;
border-radius: var(--radius-btn);
background-color: var(--accent-color);
color: #0A1A10;
font-size: 0.95rem;
font-weight: 700;
margin-bottom: 10px;
}
.support-btn-enable:hover:not(:disabled) {
opacity: 0.88;
}
.support-btn-disable {
width: 100%;
padding: 12px;
border-radius: var(--radius-btn);
background-color: var(--red);
color: #fff;
font-size: 0.95rem;
font-weight: 700;
margin-bottom: 10px;
}
.support-btn-disable:hover:not(:disabled) {
opacity: 0.88;
}
.support-btn-done {
width: 100%;
padding: 12px;
border-radius: var(--radius-btn);
background-color: var(--accent-color);
color: #0A1A10;
font-size: 0.95rem;
font-weight: 700;
margin-top: 16px;
}
.support-btn-done:hover:not(:disabled) {
opacity: 0.88;
}
.support-btn-auditlog {
width: 100%;
padding: 10px;
border-radius: var(--radius-btn);
background-color: var(--border-color);
color: var(--text-primary);
font-size: 0.85rem;
font-weight: 600;
margin-top: 8px;
}
.support-btn-auditlog:hover:not(:disabled) {
background-color: #1c2a24;
}
.support-fine-print {
font-size: 0.72rem;
color: var(--text-dim);
font-style: italic;
margin-bottom: 8px;
}
.support-verify-box {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin: 16px 0;
padding: 12px;
background-color: var(--card-color);
border-radius: 8px;
}
.support-verify-label {
font-size: 0.82rem;
color: var(--text-dim);
font-weight: 600;
}
.support-verify-value {
font-size: 0.88rem;
font-weight: 700;
}
.support-verify-value.verified-gone {
color: var(--green);
}
.support-verify-value.verify-warning {
color: var(--yellow);
}
/* ── Wallet protection ───────────────────────────────────────────── */
.support-wallet-box {
text-align: left;
padding: 14px 18px;
border-radius: 10px;
margin-bottom: 16px;
border: 1px solid var(--border-color);
}
.support-wallet-protected {
background-color: rgba(109, 191, 139, 0.06);
border-color: rgba(109, 191, 139, 0.3);
}
.support-wallet-unlocked {
background-color: rgba(229, 165, 10, 0.06);
border-color: rgba(229, 165, 10, 0.3);
}
.support-wallet-warning {
background-color: rgba(224, 27, 36, 0.06);
border-color: rgba(224, 27, 36, 0.3);
}
.support-wallet-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.support-wallet-icon {
font-size: 1.2rem;
}
.support-wallet-title {
font-size: 0.88rem;
font-weight: 700;
color: var(--text-primary);
}
.support-wallet-desc {
font-size: 0.82rem;
color: var(--text-secondary);
line-height: 1.5;
margin-bottom: 8px;
}
.support-wallet-paths {
list-style: none;
padding: 0;
margin: 8px 0;
}
.support-wallet-paths li {
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
font-size: 0.78rem;
color: var(--text-dim);
padding: 2px 0;
}
.support-wallet-unlock-row {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.support-unlock-select {
background-color: var(--card-color);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 6px 10px;
font-size: 0.82rem;
}
.support-btn-wallet-unlock {
padding: 8px 16px;
border-radius: var(--radius-btn);
background-color: var(--yellow);
color: #0A1A10;
font-size: 0.82rem;
font-weight: 700;
}
.support-btn-wallet-unlock:hover:not(:disabled) {
background-color: #c98d08;
}
.support-btn-wallet-lock {
padding: 8px 16px;
border-radius: var(--radius-btn);
background-color: var(--green);
color: #fff;
font-size: 0.82rem;
font-weight: 700;
margin-top: 8px;
}
.support-btn-wallet-lock:hover:not(:disabled) {
background-color: #529E7E;
}
/* ── Audit log ───────────────────────────────────────────────────── */
.support-audit-container {
margin-top: 12px;
border-top: 1px solid var(--border-color);
padding-top: 12px;
}
.support-audit-log {
max-height: 200px;
overflow-y: auto;
background-color: #0c0f0e;
border-radius: 8px;
padding: 10px 14px;
}
.support-audit-entry {
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
font-size: 0.72rem;
color: var(--text-secondary);
padding: 3px 0;
border-bottom: 1px solid rgba(30, 45, 39, 0.4);
}
.support-audit-entry:last-child {
border-bottom: none;
}
.support-audit-empty {
font-size: 0.82rem;
color: var(--text-dim);
text-align: center;
padding: 12px;
}
/* ── Tech Support tile ───────────────────────────────────────────── */
.support-tile {
border-color: var(--border-color);
border-width: 1px;
border-style: solid;
}
.support-tile:hover {
border-color: var(--accent-color);
border-style: solid;
}
/* ── Manual Backup ───────────────────────────────────────────────── */
.support-backup-steps {
padding-left: 20px;
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.8;
margin: 8px 0 0 0;
}
.support-backup-steps li {
margin-bottom: 4px;
}
@@ -1,410 +0,0 @@
/* ── Service tile card (status-only) ─────────────────────────────── */
.service-tile {
width: 160px;
min-height: 130px;
background-color: var(--card-color);
border: 1px solid var(--border-color);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-radius: var(--radius-card);
box-shadow: var(--shadow-card);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px 12px 18px;
gap: 0;
transition: box-shadow 0.2s, border-color 0.2s;
position: relative;
cursor: pointer;
}
.service-tile:hover {
box-shadow: var(--shadow-hover);
border-color: var(--accent-color);
}
.service-tile.disabled {
opacity: 0.45;
}
.tile-icon {
width: 48px;
height: 48px;
object-fit: contain;
margin-bottom: 10px;
}
.tile-icon-fallback {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--border-color);
border-radius: 12px;
color: var(--text-dim);
font-size: 1.5rem;
margin-bottom: 10px;
}
.tile-name {
font-size: 0.88rem;
font-weight: 600;
text-align: center;
color: var(--text-primary);
line-height: 1.3;
max-width: 140px;
word-break: break-word;
hyphens: auto;
min-height: 1.3em;
display: flex;
align-items: center;
justify-content: center;
}
.tile-status {
font-size: 0.75rem;
margin-top: 8px;
display: flex;
align-items: center;
gap: 5px;
color: var(--text-secondary);
}
.tile-version {
font-size: 0.7rem;
color: var(--text-dim);
margin-top: 2px;
text-align: center;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
background-color: var(--grey);
}
.status-dot.active { background-color: var(--green); }
.status-dot.inactive { background-color: var(--red); }
.status-dot.loading { background-color: var(--yellow); animation: pulse-badge 1s infinite; }
.status-dot.failed { background-color: var(--red); }
.status-dot.disabled { background-color: var(--grey); }
.status-dot.needs-attention { background-color: var(--yellow); }
.status-dot.syncing { background-color: #f5a623; animation: pulse-badge 1.5s infinite; }
.status-dot.checking-reachability { background-color: var(--accent-color); animation: pulse-badge 1s infinite; }
/* ── Bitcoin IBD sync progress bar ──────────────────────────────── */
.tile-sync-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
width: 100%;
margin-top: 6px;
}
.tile-sync-label {
font-size: 0.72rem;
color: #f5a623;
font-weight: 600;
text-align: center;
white-space: nowrap;
}
.tile-sync-bar-row {
display: flex;
align-items: center;
gap: 5px;
width: 100%;
}
.tile-sync-bar-track {
flex: 1;
height: 6px;
background-color: var(--border-color);
border-radius: 3px;
overflow: hidden;
}
.tile-sync-bar-fill {
height: 100%;
background-color: #f5a623;
border-radius: 3px;
transition: width 0.6s ease;
min-width: 2px;
}
.tile-sync-percent {
font-size: 0.72rem;
font-weight: 700;
color: #f5a623;
white-space: nowrap;
min-width: 2.5em;
text-align: right;
}
.tile-sync-eta {
font-size: 0.68rem;
color: var(--text-dim);
text-align: center;
white-space: nowrap;
}
/* ── Service detail modal sections ───────────────────────────────── */
.svc-detail-section {
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border-color);
}
.svc-detail-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.svc-detail-section-title {
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-dim);
margin-bottom: 10px;
}
.svc-detail-desc {
font-size: 0.9rem;
color: var(--text-secondary);
line-height: 1.6;
}
.svc-detail-status {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
font-weight: 600;
color: var(--text-primary);
}
/* ── Service detail: Domain ──────────────────────────────────────── */
.svc-detail-domain-value {
font-size: 0.9rem;
color: var(--text-primary);
font-weight: 600;
}
.tile-domain-label--ok {
color: var(--green);
font-weight: 600;
}
.tile-domain-label--warn {
color: var(--yellow);
font-weight: 600;
}
.tile-domain-label--error {
color: var(--red);
font-weight: 600;
}
/* ── Service detail: Port table ──────────────────────────────────── */
.svc-detail-port-table {
width: 100%;
border-collapse: collapse;
font-size: 0.82rem;
margin-top: 8px;
}
.svc-detail-port-table th {
text-align: left;
color: var(--text-dim);
font-weight: 600;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 6px 10px;
border-bottom: 1px solid var(--border-color);
}
.svc-detail-port-table td {
padding: 8px 10px;
border-bottom: 1px solid rgba(30, 45, 39, 0.6);
color: var(--text-primary);
}
.svc-detail-port-table tr:last-child td {
border-bottom: none;
}
.svc-detail-port-table-port {
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
font-weight: 600;
color: var(--accent-color);
}
.svc-detail-port-table-proto {
text-transform: uppercase;
color: var(--text-secondary);
}
.svc-detail-port-table-desc {
color: var(--text-secondary);
}
.svc-detail-port-table-status {
font-weight: 600;
}
.port-status-listening { color: var(--green); }
.port-status-open { color: var(--yellow); }
.port-status-closed { color: var(--red); }
.port-status-unknown { color: var(--text-dim); }
/* ── Service detail: Troubleshoot box ────────────────────────────── */
.svc-detail-troubleshoot {
margin-top: 12px;
padding: 14px 16px;
background-color: rgba(229, 165, 10, 0.08);
border: 1px solid rgba(229, 165, 10, 0.3);
border-radius: 10px;
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.6;
}
.svc-detail-troubleshoot strong {
color: var(--yellow);
}
.svc-detail-troubleshoot ol {
margin-top: 8px;
padding-left: 20px;
}
.svc-detail-troubleshoot li {
margin-bottom: 4px;
}
.svc-detail-troubleshoot code {
background-color: rgba(94, 173, 138, 0.10);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.82rem;
color: var(--accent-color);
}
.svc-detail-troubleshoot a {
color: var(--accent-color);
text-decoration: none;
}
.svc-detail-troubleshoot a:hover {
text-decoration: underline;
}
/* ── Service detail: Domain configure button ─────────────────────── */
.svc-detail-domain-btn {
margin-top: 12px;
}
/* ── Service detail: Addon feature toggle ────────────────────────── */
.svc-detail-addon-row {
display: flex;
align-items: center;
gap: 14px;
margin-top: 12px;
}
.svc-detail-addon-status {
font-size: 0.88rem;
font-weight: 700;
}
.addon-status--on {
color: var(--green);
}
.addon-status--off {
color: var(--text-dim);
}
.feature-conflict-warning {
margin-top: 8px;
margin-bottom: 8px;
padding: 10px 14px;
background-color: rgba(229, 165, 10, 0.1);
border: 1px solid rgba(229, 165, 10, 0.3);
border-radius: 8px;
font-size: 0.82rem;
color: var(--yellow);
font-weight: 600;
}
.btn-warning {
background-color: #d97706;
color: #fff;
}
.btn-warning:hover:not(:disabled) {
background-color: #b45309;
}
.svc-detail-restart-section {
border-top: 1px solid var(--border-color);
padding-top: 16px;
}
.svc-detail-restart-btn {
margin-top: 8px;
}
.svc-detail-restart-result {
margin-top: 12px;
padding: 12px 16px;
border-radius: 8px;
font-size: 0.88rem;
line-height: 1.5;
display: none;
}
.svc-detail-restart-result.success {
background-color: rgba(109, 191, 139, 0.12);
border: 1px solid var(--green);
color: var(--green);
display: block;
}
.svc-detail-restart-result.error {
background-color: rgba(239, 68, 68, 0.12);
border: 1px solid #ef4444;
color: #f87171;
display: block;
}
/* ── Desktop launch buttons ──────────────────────────────────────── */
.svc-detail-launch-row {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.svc-detail-launch-btn {
font-size: 0.85rem;
padding: 8px 18px;
cursor: pointer;
}
@@ -1,236 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="128px"
height="128px"
viewBox="0 0 128 128"
version="1.1"
id="svg96"
sodipodi:docname="Sovran_SystemsOS_Updater_Iconv3.svg"
xml:space="preserve"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview98"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="5.2149125"
inkscape:cx="9.0126153"
inkscape:cy="64.430611"
inkscape:window-width="3440"
inkscape:window-height="1352"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><defs
id="defs67"><linearGradient
inkscape:collect="always"
id="linearGradient936"><stop
style="stop-color:#1e8e11;stop-opacity:1;"
offset="0"
id="stop932" /><stop
style="stop-color:#1bff00;stop-opacity:0;"
offset="1"
id="stop934" /></linearGradient><linearGradient
id="linearGradient1028"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop1026" /></linearGradient><linearGradient
id="linearGradient998"
inkscape:swatch="solid"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop996" /></linearGradient><radialGradient
id="radial0"
gradientUnits="userSpaceOnUse"
cx="131.914749"
cy="55.927143"
fx="131.914749"
fy="55.927143"
r="160"
gradientTransform="matrix(0.232034,-0.541475,-0.368794,-0.0298398,4.277749,118.95849)"><stop
offset="0"
style="stop-color:#00ff39;stop-opacity:1;"
id="stop2" /><stop
offset="1"
style="stop-color:#004a19;stop-opacity:1;"
id="stop4" /></radialGradient><radialGradient
id="radial1"
gradientUnits="userSpaceOnUse"
cx="525.587769"
cy="638.591797"
fx="525.587769"
fy="638.591797"
r="192"
gradientTransform="matrix(-0.107656,-0.225172,-0.327748,0.258343,373.87973,30.205086)"><stop
offset="0"
style="stop-color:#43b60b;stop-opacity:1;"
id="stop7" /><stop
offset="1"
style="stop-color:#0b88ff;stop-opacity:0.00829875;"
id="stop9" /></radialGradient><clipPath
id="clip1"><path
d="M 7 46 L 57 46 L 57 93 L 7 93 Z M 7 46 "
id="path12" /></clipPath><clipPath
id="clip2"><path
d="M 32.25 46.957031 C 19.6875 46.96875 9.085938 56.636719 7.503906 69.53125 C 9.0625 82.445312 19.667969 92.144531 32.25 92.160156 C 44.816406 92.148438 55.414062 82.480469 57 69.585938 C 55.441406 56.671875 44.835938 46.972656 32.25 46.957031 Z M 32.25 46.957031 "
id="path15" /></clipPath><radialGradient
id="radial2"
gradientUnits="userSpaceOnUse"
cx="131.914749"
cy="55.927143"
fx="131.914749"
fy="55.927143"
r="160"
gradientTransform="matrix(0.485163,-1.148584,-0.771115,-0.0632965,-47.124961,203.98857)"><stop
offset="0"
style="stop-color:rgb(92.941177%,20%,23.137255%);stop-opacity:1;"
id="stop18" /><stop
offset="1"
style="stop-color:rgb(63.921571%,27.843139%,72.941178%);stop-opacity:1;"
id="stop20" /></radialGradient><radialGradient
id="radial3"
gradientUnits="userSpaceOnUse"
cx="525.587769"
cy="638.591797"
fx="525.587769"
fy="638.591797"
r="192"
gradientTransform="matrix(-0.225099,-0.477638,-0.685291,0.548001,725.67923,15.723794)"><stop
offset="0"
style="stop-color:rgb(10.980392%,44.313726%,84.705883%);stop-opacity:1;"
id="stop23" /><stop
offset="1"
style="stop-color:rgb(20.784314%,51.764709%,89.411765%);stop-opacity:0.00829876;"
id="stop25" /></radialGradient><linearGradient
id="linear0"
gradientUnits="userSpaceOnUse"
x1="22"
y1="37"
x2="62"
y2="37"
gradientTransform="matrix(1.4,0,0,1.4,-26.799973,2.491745)"><stop
offset="0"
style="stop-color:rgb(58.039218%,57.647061%,56.470591%);stop-opacity:1;"
id="stop28" /><stop
offset="0.0908155"
style="stop-color:rgb(87.058824%,86.666667%,85.490197%);stop-opacity:1;"
id="stop30" /><stop
offset="0.336093"
style="stop-color:rgb(60.392159%,60.000002%,58.823532%);stop-opacity:1;"
id="stop32" /><stop
offset="0.844326"
style="stop-color:rgb(76.47059%,75.294119%,72.941178%);stop-opacity:1;"
id="stop34" /><stop
offset="0.930505"
style="stop-color:rgb(87.058824%,86.666667%,85.490197%);stop-opacity:1;"
id="stop36" /><stop
offset="1"
style="stop-color:rgb(75.294119%,74.901962%,73.725492%);stop-opacity:1;"
id="stop38" /></linearGradient><radialGradient
id="radial4"
gradientUnits="userSpaceOnUse"
cx="-172.560638"
cy="28.569126"
fx="-172.560638"
fy="28.569126"
r="15.85742"
gradientTransform="matrix(1.560712,0,0,1.4252,300.69366,13.349996)"><stop
offset="0"
style="stop-color:rgb(100%,100%,100%);stop-opacity:0.358268;"
id="stop41" /><stop
offset="1"
style="stop-color:rgb(100%,100%,100%);stop-opacity:0.0944882;"
id="stop43" /></radialGradient><filter
id="alpha"
filterUnits="objectBoundingBox"
x="0"
y="0"
width="1"
height="1"><feColorMatrix
type="matrix"
in="SourceGraphic"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"
id="feColorMatrix46" /></filter><mask
id="mask0"><g
filter="url(#alpha)"
id="g51"><rect
x="0"
y="0"
width="128"
height="128"
style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"
id="rect49" /></g></mask><clipPath
id="clip3"><rect
x="0"
y="0"
width="192"
height="152"
id="rect54" /></clipPath><g
id="surface382"
clip-path="url(#clip3)"><path
style=" stroke:none;fill-rule:nonzero;fill:rgb(27.058825%,21.176471%,21.568628%);fill-opacity:1;"
d="M 40 59.957031 C 26.191406 59.957031 15 71.152344 15 84.957031 C 15.011719 85.996094 15.085938 86.777344 15.222656 87.804688 C 15.222656 75.957031 27.421875 65.96875 40 65.957031 C 52.597656 65.972656 64.777344 75.957031 64.777344 87.859375 C 64.917969 86.816406 64.992188 86.011719 65 84.957031 C 65 71.152344 53.808594 59.957031 40 59.957031 Z M 40 59.957031 "
id="path57" /></g><radialGradient
id="radial5"
gradientUnits="userSpaceOnUse"
cx="40"
cy="227"
fx="40"
fy="227"
r="28"
gradientTransform="matrix(0.575553,0,1.60551e-8,1.540703,8.977913,-280.78108)"><stop
offset="0"
style="stop-color:rgb(100%,100%,100%);stop-opacity:1;"
id="stop60" /><stop
offset="0.744626"
style="stop-color:rgb(98.039216%,98.039216%,98.039216%);stop-opacity:1;"
id="stop62" /><stop
offset="1"
style="stop-color:rgb(87.450981%,87.450981%,87.450981%);stop-opacity:1;"
id="stop64" /></radialGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient936"
id="linearGradient938"
x1="-48.519272"
y1="18.511358"
x2="287.07454"
y2="18.511358"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1020247,0,0,1.1097375,37.198581,-10.424856)" /></defs><path
style="fill:#f5f5f3;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 20,11.957031 h 88 c 4.41797,0 8,3.582031 8,8 V 108 c 0,4.41797 -3.58203,8 -8,8 H 20 c -4.417969,0 -8,-3.58203 -8,-8 V 19.957031 c 0,-4.417969 3.582031,-8 8,-8 z m 0,0"
id="path69" /><path
style="fill:url(#radial0);fill-rule:nonzero;stroke:none"
d="m 20,85.957031 h 88 v -66 H 20 Z m 0,0"
id="path71" /><path
style="fill:none;fill-rule:nonzero;stroke:none;fill-opacity:1"
d="m 20,85.957031 h 88 v -66 H 20 Z m 0,0"
id="path73" /><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 1"
transform="matrix(0.1816,0,0,0.1816,35.224187,79.037164)"><ellipse
fill="#54c147"
cx="168.64549"
cy="10.117889"
id="circle8314"
rx="184.91634"
ry="179.91556"
style="fill:url(#linearGradient938);fill-opacity:1;stroke-width:1.71591" /><polygon
fill="#ffffff"
points="46.678,120.299 63.562,96.402 96.977,121.718 145.084,50.79 168.752,69.02 103.583,164.647 "
id="polygon8316"
transform="matrix(1.7395866,0,0,1.6925423,-18.737581,-172.19767)" /></g></svg>

Before

Width:  |  Height:  |  Size: 8.4 KiB

@@ -1,34 +0,0 @@
/* Sovran_SystemsOS Hub — Vanilla JS Frontend
v7 — Status-only dashboard + Tech Support + Feature Manager */
"use strict";
const POLL_INTERVAL_SERVICES = 5000;
const POLL_INTERVAL_UPDATES = 1800000;
const UPDATE_POLL_INTERVAL = 2000;
const REBOOT_CHECK_INTERVAL = 5000;
const REBOOT_FETCH_TIMEOUT = 12000;
const REBOOT_REQUEST_TIMEOUT = 4000;
const REBOOT_INITIAL_DELAY = 25000;
const SUPPORT_TIMER_INTERVAL = 1000;
const CATEGORY_ORDER = [
"infrastructure",
"bitcoin-base",
"bitcoin-apps",
"communication",
"apps",
"nostr",
];
const FEATURE_SUBCATEGORY_LABELS = {
"infrastructure": "🔧 Infrastructure",
"bitcoin": "₿ Bitcoin",
"communication": "💬 Communication",
"nostr": "📡 Nostr",
};
const FEATURE_SUBCATEGORY_ORDER = ["infrastructure", "bitcoin", "communication", "nostr"];
const STATUS_LOADING_STATES = new Set([
"reloading", "activating", "deactivating", "maintenance",
]);
@@ -1,191 +0,0 @@
"use strict";
// ── Event listeners ───────────────────────────────────────────────
// if ($updateBtn) $updateBtn.addEventListener("click", openUpdateModal); // moved to sidebar in tiles.js
if ($btnCloseModal) $btnCloseModal.addEventListener("click", closeUpdateModal);
if ($btnReboot) $btnReboot.addEventListener("click", doReboot);
if ($btnSave) $btnSave.addEventListener("click", saveErrorReport);
if ($credsCloseBtn) $credsCloseBtn.addEventListener("click", closeCredsModal);
if ($supportCloseBtn) $supportCloseBtn.addEventListener("click", closeSupportModal);
// Logout button
if ($logoutBtn) $logoutBtn.addEventListener("click", function () {
fetch("/api/logout", { method: "POST", credentials: "same-origin" })
.finally(function () { window.location.replace("/login"); });
});
// Rebuild modal
if ($rebuildClose) $rebuildClose.addEventListener("click", closeRebuildModal);
if ($rebuildReboot) $rebuildReboot.addEventListener("click", doReboot);
if ($rebuildSave) $rebuildSave.addEventListener("click", saveRebuildErrorReport);
if ($rebuildModal) $rebuildModal.addEventListener("click", function(e) { if (e.target === $rebuildModal) closeRebuildModal(); });
// Domain setup modal
if ($domainSetupClose) $domainSetupClose.addEventListener("click", closeDomainSetupModal);
if ($domainSetupModal) $domainSetupModal.addEventListener("click", function(e) { if (e.target === $domainSetupModal) closeDomainSetupModal(); });
// SSL Email modal
if ($sslEmailClose) $sslEmailClose.addEventListener("click", closeSslEmailModal);
if ($sslEmailCancel) $sslEmailCancel.addEventListener("click", closeSslEmailModal);
if ($sslEmailModal) $sslEmailModal.addEventListener("click", function(e) { if (e.target === $sslEmailModal) closeSslEmailModal(); });
// Feature confirm modal
if ($featureConfirmClose) $featureConfirmClose.addEventListener("click", closeFeatureConfirm);
if ($featureConfirmCancel) $featureConfirmCancel.addEventListener("click", closeFeatureConfirm);
if ($featureConfirmModal) $featureConfirmModal.addEventListener("click", function(e) { if (e.target === $featureConfirmModal) closeFeatureConfirm(); });
if ($modal) $modal.addEventListener("click", function(e) { if (e.target === $modal) closeUpdateModal(); });
if ($credsModal) $credsModal.addEventListener("click", function(e) { if (e.target === $credsModal) closeCredsModal(); });
if ($supportModal) $supportModal.addEventListener("click", function(e) { if (e.target === $supportModal) closeSupportModal(); });
// Upgrade modal
if ($upgradeCloseBtn) $upgradeCloseBtn.addEventListener("click", closeUpgradeModal);
if ($upgradeCancelBtn) $upgradeCancelBtn.addEventListener("click", closeUpgradeModal);
if ($upgradeModal) $upgradeModal.addEventListener("click", function(e) { if (e.target === $upgradeModal) closeUpgradeModal(); });
// ── Upgrade modal functions ───────────────────────────────────────
function openUpgradeModal() {
if ($upgradeModal) $upgradeModal.classList.add("open");
}
function closeUpgradeModal() {
if ($upgradeModal) $upgradeModal.classList.remove("open");
}
async function doUpgradeToServer() {
var confirmBtn = $upgradeConfirmBtn;
if (confirmBtn) { confirmBtn.disabled = true; confirmBtn.textContent = "Upgrading…"; }
closeUpgradeModal();
// Reuse the rebuild modal to show reboot progress
_rebuildFeatureName = "Server + Desktop";
_rebuildIsEnabling = true;
openRebuildModal();
try {
await apiFetch("/api/role/upgrade-to-server", { method: "POST" });
// Server is rebooting — show message and wait for it to come back
if ($rebuildStatus) $rebuildStatus.textContent = "Rebooting — the setup wizard will guide you through domain and port configuration…";
if ($rebuildSpinner) $rebuildSpinner.classList.add("spinning");
// Poll until server comes back, then redirect to onboarding
var pollInterval = setInterval(async function() {
try {
await apiFetch("/api/ping");
clearInterval(pollInterval);
window.location.href = "/onboarding";
} catch (_) {
// Server still down — keep polling
}
}, 3000);
} catch (err) {
if ($rebuildStatus) $rebuildStatus.textContent = "✗ Upgrade failed: " + err.message;
if ($rebuildSpinner) $rebuildSpinner.classList.remove("spinning");
if ($rebuildClose) $rebuildClose.disabled = false;
if (confirmBtn) { confirmBtn.disabled = false; confirmBtn.textContent = "Yes, Upgrade"; }
}
}
if ($upgradeConfirmBtn) $upgradeConfirmBtn.addEventListener("click", doUpgradeToServer);
// ── First-login security banner ───────────────────────────────────
function showSecurityBanner() {
var existing = document.getElementById("security-first-login-banner");
if (existing) return;
var banner = document.createElement("div");
banner.id = "security-first-login-banner";
banner.className = "security-first-login-banner";
banner.innerHTML =
'<div class="security-banner-content">' +
'<span class="security-banner-icon">\uD83D\uDEE1</span>' +
'<span class="security-banner-text">' +
'<strong>Did someone else set up this machine?</strong> ' +
'If this computer was pre-configured by another person, go to ' +
'<strong>Menu \u2192 Security</strong> to reset all passwords and keys. ' +
'This ensures only you have access.' +
'</span>' +
'</div>' +
'<button class="security-banner-dismiss" id="security-banner-dismiss-btn" title="Dismiss">\u2715</button>';
var mainContent = document.querySelector(".main-content");
if (mainContent) {
mainContent.insertAdjacentElement("beforebegin", banner);
} else {
document.body.insertAdjacentElement("afterbegin", banner);
}
var dismissBtn = document.getElementById("security-banner-dismiss-btn");
if (dismissBtn) {
dismissBtn.addEventListener("click", async function() {
banner.remove();
try {
await apiFetch("/api/security/banner-dismiss", { method: "POST" });
} catch (_) {
// Non-fatal
}
});
}
}
// ── Init ──────────────────────────────────────────────────────────
async function init() {
// Check onboarding status first — redirect to wizard if not complete
try {
var onboardingStatus = await apiFetch("/api/onboarding/status");
if (!onboardingStatus.complete) {
window.location.href = "/onboarding";
return;
}
} catch (_) {
// If we can't reach the endpoint, continue to normal dashboard
}
// Show first-login security banner only for machines that went through onboarding
// (legacy machines without the onboarding flag will never see this)
try {
var bannerData = await apiFetch("/api/security/banner-status");
if (bannerData && bannerData.show) {
showSecurityBanner();
}
} catch (_) {
// Non-fatal — silently ignore
}
try {
var cfg = await apiFetch("/api/config");
_currentRole = cfg.role || "server_plus_desktop";
if (cfg.category_order) {
for (var i = 0; i < cfg.category_order.length; i++) {
_categoryLabels[cfg.category_order[i][0]] = cfg.category_order[i][1];
}
}
var badge = document.getElementById("role-badge");
if (badge && cfg.role_label) badge.textContent = cfg.role_label;
await refreshServices();
loadNetwork();
checkUpdates();
setInterval(refreshServices, POLL_INTERVAL_SERVICES);
setInterval(checkUpdates, POLL_INTERVAL_UPDATES);
if (cfg.feature_manager) {
loadFeatureManager();
}
loadAutolaunchToggle();
} catch (_) {
await refreshServices();
loadNetwork();
checkUpdates();
setInterval(refreshServices, POLL_INTERVAL_SERVICES);
setInterval(checkUpdates, POLL_INTERVAL_UPDATES);
loadAutolaunchToggle();
}
}
document.addEventListener("DOMContentLoaded", init);
@@ -1,699 +0,0 @@
"use strict";
// ── Feature confirm modal ─────────────────────────────────────────
function openFeatureConfirm(message, onConfirm) {
if (!$featureConfirmModal) return;
if ($featureConfirmMsg) $featureConfirmMsg.textContent = message;
$featureConfirmModal.classList.add("open");
// Replace ok handler
var newOk = $featureConfirmOk.cloneNode(true);
$featureConfirmOk.parentNode.replaceChild(newOk, $featureConfirmOk);
newOk.addEventListener("click", function() {
closeFeatureConfirm();
onConfirm();
});
}
function closeFeatureConfirm() {
if ($featureConfirmModal) $featureConfirmModal.classList.remove("open");
}
// ── SSL Email modal ───────────────────────────────────────────────
function openSslEmailModal(onSaved) {
if (!$sslEmailModal) return;
if ($sslEmailInput) $sslEmailInput.value = "";
$sslEmailModal.classList.add("open");
// Replace save handler
var newSave = $sslEmailSave.cloneNode(true);
$sslEmailSave.parentNode.replaceChild(newSave, $sslEmailSave);
newSave.addEventListener("click", async function() {
var email = $sslEmailInput ? $sslEmailInput.value.trim() : "";
if (!email) { alert("Please enter an email address."); return; }
newSave.disabled = true;
newSave.textContent = "Saving…";
try {
await apiFetch("/api/domains/set-email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: email }),
});
closeSslEmailModal();
onSaved();
} catch (err) {
newSave.disabled = false;
newSave.textContent = "Save";
alert("Failed to save email. Please try again.");
}
});
}
function closeSslEmailModal() {
if ($sslEmailModal) $sslEmailModal.classList.remove("open");
}
// ── Domain Setup modal ────────────────────────────────────────────
function openDomainSetupModal(feat, onSaved) {
if (!$domainSetupModal) return;
if ($domainSetupTitle) $domainSetupTitle.textContent = "🌐 Domain Setup — " + feat.name;
var npubField = "";
if (feat.id === "haven") {
var currentNpub = "";
if (feat.extra_fields && feat.extra_fields.length > 0) {
for (var i = 0; i < feat.extra_fields.length; i++) {
if (feat.extra_fields[i].id === "nostr_npub") {
currentNpub = feat.extra_fields[i].current_value || "";
break;
}
}
}
npubField = '<div class="domain-field-group"><label class="domain-field-label" for="domain-npub-input">Nostr Public Key (npub1...):</label><input class="domain-field-input" type="text" id="domain-npub-input" placeholder="npub1..." value="' + escHtml(currentNpub) + '" /></div>';
}
var externalIp = _cachedExternalIp || "your external IP";
$domainSetupBody.innerHTML =
'<div class="domain-setup-intro">' +
'<p><strong>Before continuing:</strong></p>' +
'<ol>' +
'<li>Create an account at <a href="https://njal.la" target="_blank" rel="noopener noreferrer" style="color:var(--accent-color);">https://njal.la</a></li>' +
'<li>Purchase a new domain on Njal.la, or create a subdomain from a domain you already own. Tip: Subdomains are free to create — you only need to purchase one domain, and you can add as many subdomains as you need at no extra cost.</li>' +
'<li>In the Njal.la web interface, create a <strong>Dynamic</strong> record pointing to this machine\'s external IP address:<br>' +
'<span style="display:inline-block;margin-top:4px;padding:4px 10px;background:var(--card-color);border:1px solid var(--border-color);border-radius:6px;font-family:monospace;font-size:1em;font-weight:700;">' + escHtml(externalIp) + '</span></li>' +
'<li>Njal.la will give you a curl command like:<br>' +
'<code style="font-size:0.8em;">curl &quot;https://njal.la/update/?h=sub.domain.com&amp;k=abc123&amp;auto&quot;</code></li>' +
'<li>Enter the subdomain and paste that curl command below</li>' +
'</ol>' +
'</div>' +
'<div class="domain-field-group"><label class="domain-field-label" for="domain-subdomain-input">Subdomain (e.g. myservice.example.com):</label><input class="domain-field-input" type="text" id="domain-subdomain-input" placeholder="myservice.example.com" /></div>' +
'<div class="domain-field-group"><label class="domain-field-label" for="domain-ddns-input">Njal.la Dynamic DNS Update Command:</label><input class="domain-field-input" type="text" id="domain-ddns-input" placeholder="curl &quot;https://njal.la/update/?h=myservice.example.com&amp;k=abc123&amp;auto&quot;" /><p class="domain-field-hint"> Paste the full curl command from your Njal.la dashboard\'s Dynamic record</p></div>' +
npubField +
'<div class="domain-field-actions"><button class="btn btn-close-modal" id="domain-setup-cancel-btn">Cancel</button><button class="btn btn-primary" id="domain-setup-save-btn">Save &amp; Enable</button></div>';
document.getElementById("domain-setup-cancel-btn").addEventListener("click", closeDomainSetupModal);
document.getElementById("domain-setup-save-btn").addEventListener("click", async function() {
var subdomain = (document.getElementById("domain-subdomain-input") || {}).value || "";
var ddnsUrl = (document.getElementById("domain-ddns-input") || {}).value || "";
var npub = document.getElementById("domain-npub-input") ? (document.getElementById("domain-npub-input").value || "") : "";
subdomain = subdomain.trim();
ddnsUrl = ddnsUrl.trim();
npub = npub.trim();
if (!subdomain) { alert("Please enter a subdomain."); return; }
if (feat.id === "haven" && !npub) { alert("Please enter your Nostr public key."); return; }
var saveBtn = document.getElementById("domain-setup-save-btn");
saveBtn.disabled = true;
saveBtn.textContent = "Saving…";
try {
await apiFetch("/api/domains/set", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
domain_name: feat.domain_name,
domain: subdomain,
ddns_url: ddnsUrl,
}),
});
closeDomainSetupModal();
onSaved(npub);
} catch (err) {
saveBtn.disabled = false;
saveBtn.textContent = "Save & Enable";
alert("Failed to save domain. Please try again.");
}
});
$domainSetupModal.classList.add("open");
}
function openDomainReconfigureModal(feat, existingDomain, onSaved) {
if (!$domainSetupModal) return;
if ($domainSetupTitle) $domainSetupTitle.textContent = "🔄 Reconfigure Domain — " + feat.name;
var npubField = "";
if (feat.id === "haven") {
var currentNpub = "";
if (feat.extra_fields && feat.extra_fields.length > 0) {
for (var i = 0; i < feat.extra_fields.length; i++) {
if (feat.extra_fields[i].id === "nostr_npub") {
currentNpub = feat.extra_fields[i].current_value || "";
break;
}
}
}
npubField = '<div class="domain-field-group"><label class="domain-field-label" for="domain-npub-input">Nostr Public Key (npub1...):</label><input class="domain-field-input" type="text" id="domain-npub-input" placeholder="npub1..." value="' + escHtml(currentNpub) + '" /></div>';
}
var externalIp = _cachedExternalIp || "your external IP";
var currentDomain = existingDomain || "";
$domainSetupBody.innerHTML =
'<div class="domain-setup-intro">' +
'<p>Your domain <strong>' + escHtml(currentDomain || "this domain") + '</strong> is configured but isn\'t resolving correctly.</p>' +
'<p><strong>Troubleshooting steps:</strong></p>' +
'<ol>' +
'<li>Log into your Njal.la dashboard at <a href="https://njal.la" target="_blank" rel="noopener noreferrer" style="color:var(--accent-color);">https://njal.la</a></li>' +
'<li>Find the DNS record for <strong>' + escHtml(currentDomain || "your domain") + '</strong></li>' +
'<li>Verify it has a <strong>Dynamic</strong> record pointing to your current external IP:<br>' +
'<span style="display:inline-block;margin-top:4px;padding:4px 10px;background:var(--card-color);border:1px solid var(--border-color);border-radius:6px;font-family:monospace;font-size:1em;font-weight:700;">' + escHtml(externalIp) + '</span></li>' +
'<li>If the IP is wrong or the record is missing, update it</li>' +
'<li>If you changed the DDNS curl command, paste the updated one below</li>' +
'</ol>' +
'</div>' +
'<div class="domain-field-group"><label class="domain-field-label" for="domain-subdomain-input">Subdomain (e.g. myservice.example.com):</label><input class="domain-field-input" type="text" id="domain-subdomain-input" placeholder="myservice.example.com" value="' + escHtml(currentDomain) + '" /></div>' +
'<div class="domain-field-group"><label class="domain-field-label" for="domain-ddns-input">Njal.la Dynamic DNS Update Command:</label><input class="domain-field-input" type="text" id="domain-ddns-input" placeholder="curl &quot;https://njal.la/update/?h=myservice.example.com&amp;k=abc123&amp;auto&quot;" /><p class="domain-field-hint"> Paste the full curl command from your Njal.la dashboard\'s Dynamic record</p></div>' +
npubField +
'<div class="domain-field-actions"><button class="btn btn-close-modal" id="domain-setup-cancel-btn">Cancel</button><button class="btn btn-primary" id="domain-setup-save-btn">Save &amp; Update</button></div>';
document.getElementById("domain-setup-cancel-btn").addEventListener("click", closeDomainSetupModal);
document.getElementById("domain-setup-save-btn").addEventListener("click", async function() {
var subdomain = (document.getElementById("domain-subdomain-input") || {}).value || "";
var ddnsUrl = (document.getElementById("domain-ddns-input") || {}).value || "";
var npub = document.getElementById("domain-npub-input") ? (document.getElementById("domain-npub-input").value || "") : "";
subdomain = subdomain.trim();
ddnsUrl = ddnsUrl.trim();
npub = npub.trim();
if (!subdomain) { alert("Please enter a subdomain."); return; }
if (feat.id === "haven" && !npub) { alert("Please enter your Nostr public key."); return; }
var saveBtn = document.getElementById("domain-setup-save-btn");
saveBtn.disabled = true;
saveBtn.textContent = "Saving…";
try {
await apiFetch("/api/domains/set", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
domain_name: feat.domain_name,
domain: subdomain,
ddns_url: ddnsUrl,
}),
});
closeDomainSetupModal();
onSaved(npub);
} catch (err) {
saveBtn.disabled = false;
saveBtn.textContent = "Save & Update";
alert("Failed to save domain. Please try again.");
}
});
$domainSetupModal.classList.add("open");
}
function closeDomainSetupModal() {
if ($domainSetupModal) $domainSetupModal.classList.remove("open");
}
// ── Port Requirements modal ───────────────────────────────────────
function openPortRequirementsModal(featureName, ports, onContinue) {
if (!$portReqModal || !$portReqBody) return;
var continueBtn = onContinue
? '<button class="btn btn-primary" id="port-req-continue-btn">I Understand — Continue</button>'
: '';
function renderPortRequirements(internalIp) {
var rows = ports.map(function(p) {
return '<tr><td class="port-req-port">' + escHtml(p.port) + '</td>' +
'<td class="port-req-proto">' + escHtml(p.protocol) + '</td>' +
'<td class="port-req-desc">' + escHtml(p.description) + '</td></tr>';
}).join("");
var ipLine = internalIp
? '<p class="port-req-intro">Forward each port below <strong>to this machine\'s internal IP: <code class="port-req-internal-ip">' + escHtml(internalIp) + '</code></strong></p>'
: "<p class=\"port-req-intro\">Forward each port below to this machine's internal LAN IP in your router's port forwarding settings.</p>";
$portReqBody.innerHTML =
'<p class="port-req-intro"><strong>Port Forwarding Required</strong></p>' +
'<p class="port-req-intro">For <strong>' + escHtml(featureName) + "</strong> to work with clients outside your local network, " +
"you must configure <strong>port forwarding</strong> in your router's admin panel.</p>" +
ipLine +
'<table class="port-req-table">' +
'<thead><tr><th>Port(s)</th><th>Protocol</th><th>Purpose</th></tr></thead>' +
'<tbody>' + rows + '</tbody>' +
'</table>' +
"<p class=\"port-req-hint\"><strong>How to verify:</strong> Router-side forwarding cannot be checked from inside your network. " +
"To confirm ports are forwarded correctly, test from a device on a different network (e.g. a phone on mobile data) " +
"or check your router's port forwarding page.</p>" +
'<p class="port-req-hint"> Search "<em>how to set up port forwarding on [your router model]</em>" for step-by-step instructions.</p>' +
'<div class="domain-field-actions">' +
'<button class="btn btn-close-modal" id="port-req-dismiss-btn">Dismiss</button>' +
continueBtn +
'</div>';
document.getElementById("port-req-dismiss-btn").onclick = function() {
closePortRequirementsModal();
};
if (onContinue) {
document.getElementById("port-req-continue-btn").onclick = function() {
closePortRequirementsModal();
onContinue();
};
}
}
$portReqModal.classList.add("open");
renderPortRequirements(null);
fetch("/api/network")
.then(function(r) { return r.json(); })
.then(function(data) {
if (!$portReqModal.classList.contains("open")) return;
var internalIp = (data.internal_ip && data.internal_ip !== "unavailable")
? data.internal_ip : null;
renderPortRequirements(internalIp);
})
.catch(function(err) {
console.warn("Failed to fetch network info for port requirements modal:", err);
});
}
function closePortRequirementsModal() {
if ($portReqModal) $portReqModal.classList.remove("open");
}
if ($portReqClose) {
$portReqClose.addEventListener("click", closePortRequirementsModal);
}
// ── Feature toggle logic ──────────────────────────────────────────
async function performFeatureToggle(featId, enabled, extra) {
// Look up feature name for the rebuild modal
_rebuildIsEnabling = enabled;
_rebuildFeatureName = featId;
if (_featuresData) {
var found = _featuresData.features.find(function(f) { return f.id === featId; });
if (found) _rebuildFeatureName = found.name;
}
try {
var res = await fetch("/api/features/toggle", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ feature: featId, enabled: enabled, extra: extra || {} }),
});
var body = await res.json();
if (!res.ok) {
if (body && body.error === "domain_required") {
alert("Domain not configured for this feature. Please configure it first.");
} else {
alert("Error: " + (body.detail || body.error || "Unknown error"));
}
loadFeatureManager();
return;
}
openRebuildModal();
} catch (err) {
alert("Failed to toggle feature: " + err);
loadFeatureManager();
}
}
function handleFeatureToggle(feat, newEnabled) {
if (!newEnabled) {
// Disable: ask confirmation
openFeatureConfirm(
"This will disable " + feat.name + ". The system will rebuild. Continue?",
function() { performFeatureToggle(feat.id, false, {}); }
);
return;
}
// Enabling
var conflictNames = [];
if (feat.conflicts_with && feat.conflicts_with.length > 0 && _featuresData) {
feat.conflicts_with.forEach(function(cid) {
var cf = _featuresData.features.find(function(f) { return f.id === cid; });
if (cf && cf.enabled) conflictNames.push(cf.name);
});
}
function proceedAfterPortCheck() {
// Check SSL email first
if (!_featuresData || !_featuresData.ssl_email_configured) {
if (feat.needs_domain) {
openSslEmailModal(function() {
// After ssl email saved, check domain
checkDomainAndEnable(feat, {});
});
return;
}
}
if (feat.needs_domain && !feat.domain_configured) {
checkDomainAndEnable(feat, {});
return;
}
if (feat.id === "haven") {
var npub = "";
if (feat.extra_fields) {
var ef = feat.extra_fields.find(function(e) { return e.id === "nostr_npub"; });
if (ef) npub = ef.current_value || "";
}
if (!npub) {
// Need to collect npub via domain modal
openDomainSetupModal(feat, function(collectedNpub) {
performFeatureToggle(feat.id, true, { nostr_npub: collectedNpub });
});
return;
}
}
performFeatureToggle(feat.id, true, {});
}
function proceedAfterConflictCheck() {
var ports = feat.port_requirements || [];
if (ports.length === 0) {
proceedAfterPortCheck();
return;
}
// Check which ports are actually closed before showing the modal
fetch("/api/ports/status", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ports: ports }),
})
.then(function(r) {
if (!r.ok) throw new Error("Port status request failed: " + r.status);
return r.json();
})
.then(function(data) {
var portStatuses = {};
(data.ports || []).forEach(function(p) {
portStatuses[p.port + "/" + p.protocol] = p.status;
});
var closedPorts = ports.filter(function(p) {
var key = p.port + "/" + p.protocol;
var status = portStatuses[key] || "unknown";
return status !== "listening" && status !== "firewall_open";
});
if (closedPorts.length === 0) {
proceedAfterPortCheck();
} else {
openPortRequirementsModal(feat.name, closedPorts, proceedAfterPortCheck);
}
})
.catch(function(err) {
console.warn("Failed to fetch port status for feature enable flow:", err);
// Safe fallback if status check fails
openPortRequirementsModal(feat.name, ports, proceedAfterPortCheck);
});
}
if (conflictNames.length > 0) {
var confirmMsg;
if (feat.id === "bip110") {
confirmMsg = "Only one Bitcoin node implementation can be active. Enabling Bitcoin Knots + BIP110 will disable Bitcoin Core (if active). Your timechain data will be preserved — you will not need to re-download the timechain. Continue?";
} else if (feat.id === "bitcoin-core") {
confirmMsg = "Only one Bitcoin node implementation can be active. Enabling Bitcoin Core will disable Bitcoin Knots + BIP110 (if active). Your timechain data will be preserved — you will not need to re-download the timechain. Continue?";
} else {
confirmMsg = "This will disable " + conflictNames.join(", ") + ". Continue?";
}
openFeatureConfirm(confirmMsg, proceedAfterConflictCheck);
} else {
proceedAfterConflictCheck();
}
}
function checkDomainAndEnable(feat, extra) {
openDomainSetupModal(feat, function(collectedNpub) {
var extraData = {};
if (collectedNpub) extraData.nostr_npub = collectedNpub;
performFeatureToggle(feat.id, true, extraData);
});
}
// ── Feature Manager rendering ─────────────────────────────────────
async function loadFeatureManager() {
try {
var data = await apiFetch("/api/features");
_featuresData = data;
// Feature Manager is now integrated into tile modals; sidebar rendering removed.
} catch (err) {
console.warn("Failed to load features:", err);
}
}
function _checkFeatureManagerDomains(data) {
// Collect all features with a configured domain
var featsWithDomain = (data.features || []).filter(function(f) {
return f.needs_domain && f.domain_configured;
});
if (!featsWithDomain.length) return;
// Get the actual domain values from /api/domains/status, then check them
fetch("/api/domains/status")
.then(function(r) { return r.json(); })
.then(function(statusData) {
var domainFileMap = statusData.domains || {};
// Build list of domains to check and a map from domain value → feature id
var domainsToCheck = [];
var domainToFeatIds = {};
featsWithDomain.forEach(function(feat) {
var domainName = feat.domain_name;
var domainVal = domainName ? domainFileMap[domainName] : null;
if (domainVal) {
domainsToCheck.push(domainVal);
if (!domainToFeatIds[domainVal]) domainToFeatIds[domainVal] = [];
domainToFeatIds[domainVal].push(feat.id);
} else {
// Domain file missing — update badge to warn
_updateFeatureDomainBadge(feat.id, null, "unresolvable");
}
});
if (!domainsToCheck.length) return;
return fetch("/api/domains/check", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ domains: domainsToCheck }),
})
.then(function(r) { return r.json(); })
.then(function(checkData) {
(checkData.domains || []).forEach(function(d) {
var featIds = domainToFeatIds[d.domain] || [];
featIds.forEach(function(featId) {
_updateFeatureDomainBadge(featId, d.domain, d.status);
});
});
});
})
.catch(function() {});
}
function _updateFeatureDomainBadge(featId, domainVal, status) {
var section = $sidebarFeatures.querySelector(".feature-manager-section");
if (!section) return;
// Find the card — cards don't have a data-feat-id, so find via name match
var badges = section.querySelectorAll(".feature-domain-badge.configured");
badges.forEach(function(badge) {
var domainNameAttr = badge.getAttribute("data-domain-name");
// Match by domain_name attribute — we need to look up the feat's domain_name
var feat = _featuresData && _featuresData.features
? _featuresData.features.find(function(f) { return f.id === featId; })
: null;
if (!feat) return;
if (domainNameAttr !== (feat.domain_name || "")) return;
var lbl = badge.querySelector(".feature-domain-label");
if (!lbl) return;
lbl.classList.remove("feature-domain-label--checking");
if (status === "connected") {
lbl.className = "feature-domain-label feature-domain-label--ok";
lbl.textContent = (domainVal || "Domain") + " ✓";
} else if (status === "dns_mismatch") {
lbl.className = "feature-domain-label feature-domain-label--warn";
lbl.textContent = (domainVal || "Domain") + " (IP mismatch)";
} else if (status === "unresolvable") {
lbl.className = "feature-domain-label feature-domain-label--error";
lbl.textContent = (domainVal || "Domain") + " (DNS error)";
} else {
lbl.className = "feature-domain-label feature-domain-label--warn";
lbl.textContent = (domainVal || "Domain") + " (unknown)";
}
});
}
function renderFeatureManager(data) {
// Remove old feature manager section if it exists
var old = $sidebarFeatures.querySelector(".feature-manager-section");
if (old) old.parentNode.removeChild(old);
var section = document.createElement("div");
section.className = "category-section feature-manager-section";
section.dataset.category = "feature-manager";
section.innerHTML = '<div class="section-header">Feature Manager</div><hr class="section-divider" />';
// Group by sub-category
var grouped = {};
for (var i = 0; i < data.features.length; i++) {
var f = data.features[i];
var cat = f.category || "other";
if (!grouped[cat]) grouped[cat] = [];
grouped[cat].push(f);
}
var orderedCats = FEATURE_SUBCATEGORY_ORDER.filter(function(k) { return grouped[k]; });
Object.keys(grouped).forEach(function(k) {
if (orderedCats.indexOf(k) === -1) orderedCats.push(k);
});
for (var j = 0; j < orderedCats.length; j++) {
var catKey = orderedCats[j];
var feats = grouped[catKey];
if (!feats || feats.length === 0) continue;
var subcat = document.createElement("div");
subcat.className = "feature-subcategory";
var subcatLabel = FEATURE_SUBCATEGORY_LABELS[catKey] || catKey;
subcat.innerHTML = '<div class="feature-subcategory-header">' + escHtml(subcatLabel) + '</div>';
var cardsWrap = document.createElement("div");
cardsWrap.className = "feature-cards-wrap";
for (var k = 0; k < feats.length; k++) {
cardsWrap.appendChild(buildFeatureCard(feats[k]));
}
subcat.appendChild(cardsWrap);
section.appendChild(subcat);
}
$sidebarFeatures.appendChild(section);
}
function buildFeatureCard(feat) {
var card = document.createElement("div");
card.className = "feature-card";
var conflictHtml = "";
if (feat.conflicts_with && feat.conflicts_with.length > 0) {
var conflictNames = feat.conflicts_with.map(function(cid) {
if (!_featuresData) return cid;
var cf = _featuresData.features.find(function(f) { return f.id === cid; });
return cf ? cf.name : cid;
});
conflictHtml = '<div class="feature-conflict-warning">⚠ Conflicts with: ' + escHtml(conflictNames.join(", ")) + '</div>';
}
var domainHtml = "";
if (feat.needs_domain) {
if (feat.domain_configured) {
domainHtml = '<div class="feature-domain-badge configured" data-domain-name="' + escHtml(feat.domain_name || '') + '">'
+ '<span class="feature-domain-icon">🌐</span>'
+ '<span class="feature-domain-label feature-domain-label--checking">Domain: Checking\u2026</span>'
+ '</div>';
} else {
domainHtml = '<div class="feature-domain-badge not-configured">'
+ '<span class="feature-domain-icon">🌐</span>'
+ '<span class="feature-domain-label feature-domain-label--warn">Domain: Not configured</span>'
+ '</div>';
}
}
var statusText = feat.enabled ? "Enabled" : "Disabled";
card.innerHTML =
'<div class="feature-card-top">' +
'<div class="feature-card-info">' +
'<div class="feature-card-name">' + escHtml(feat.name) + '</div>' +
'<div class="feature-card-desc">' + escHtml(feat.description) + '</div>' +
'</div>' +
'<label class="feature-toggle' + (feat.enabled ? " active" : "") + '" title="Toggle ' + escHtml(feat.name) + '">' +
'<input type="checkbox" class="feature-toggle-input"' + (feat.enabled ? " checked" : "") + ' />' +
'<span class="feature-toggle-slider"></span>' +
'</label>' +
'</div>' +
domainHtml +
conflictHtml +
'<div class="feature-card-status">Status: ' + escHtml(statusText) + '</div>';
var toggle = card.querySelector(".feature-toggle-input");
var toggleLabel = card.querySelector(".feature-toggle");
toggle.addEventListener("change", function() {
var newEnabled = toggle.checked;
// Revert visually until confirmed
toggle.checked = feat.enabled;
if (newEnabled) { toggleLabel.classList.remove("active"); } else { toggleLabel.classList.add("active"); }
handleFeatureToggle(feat, newEnabled);
});
return card;
}
// ── Auto-launch toggle ────────────────────────────────────────────
async function loadAutolaunchToggle() {
try {
var data = await apiFetch("/api/autolaunch/status");
renderAutolaunchToggle(data.enabled);
} catch (err) {
console.warn("Failed to load autolaunch status:", err);
}
}
function renderAutolaunchToggle(enabled) {
// Remove existing section if any
var old = $sidebarFeatures.querySelector(".autolaunch-section");
if (old) old.parentNode.removeChild(old);
var section = document.createElement("div");
section.className = "category-section autolaunch-section";
section.innerHTML =
'<div class="section-header">Preferences</div>' +
'<hr class="section-divider" />' +
'<div class="feature-card">' +
'<div class="feature-card-top">' +
'<div class="feature-card-info">' +
'<div class="feature-card-name">Auto-launch Hub on Login</div>' +
'<div class="feature-card-desc">Automatically open the Sovran Hub dashboard in your browser when you log in to the desktop.</div>' +
'</div>' +
'<label class="feature-toggle' + (enabled ? " active" : "") + '" id="autolaunch-toggle-label" title="Toggle auto-launch">' +
'<input type="checkbox" class="feature-toggle-input" id="autolaunch-toggle-input"' + (enabled ? " checked" : "") + ' />' +
'<span class="feature-toggle-slider"></span>' +
'</label>' +
'</div>' +
'</div>';
$sidebarFeatures.appendChild(section);
var input = document.getElementById("autolaunch-toggle-input");
var label = document.getElementById("autolaunch-toggle-label");
if (!input || !label) return;
input.addEventListener("change", async function() {
var newEnabled = input.checked;
// Revert visually until confirmed
input.checked = !newEnabled;
if (newEnabled) { label.classList.remove("active"); } else { label.classList.add("active"); }
input.disabled = true;
try {
await apiFetch("/api/autolaunch/toggle", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: newEnabled }),
});
input.checked = newEnabled;
if (newEnabled) { label.classList.add("active"); } else { label.classList.remove("active"); }
} catch (err) {
alert("Failed to update auto-launch setting. Please try again.");
} finally {
input.disabled = false;
}
});
}
@@ -1,62 +0,0 @@
"use strict";
// ── Helpers ───────────────────────────────────────────────────────
function tileId(svc) { return svc.unit + "::" + svc.name; }
function statusClass(health) {
if (!health) return "unknown";
if (health === "healthy") return "active";
if (health === "needs_attention") return "needs-attention";
if (health === "active") return "active"; // backwards compat
if (health === "inactive") return "inactive";
if (health === "failed") return "failed";
if (health === "disabled") return "disabled";
if (health === "syncing") return "syncing";
if (STATUS_LOADING_STATES.has(health)) return "loading";
if (health === "checking_reachability") return "checking-reachability";
return "unknown";
}
function statusText(health, enabled) {
if (!enabled) return "Disabled";
if (health === "healthy") return "Active";
if (health === "needs_attention") return "Needs Attention";
if (health === "active") return "Active";
if (health === "inactive") return "Inactive";
if (health === "failed") return "Failed";
if (health === "syncing") return "Syncing\u2026";
if (!health || health === "unknown") return "Unknown";
if (STATUS_LOADING_STATES.has(health)) return health;
if (health === "checking_reachability") return "Checking\u2026";
return health;
}
function escHtml(str) {
return String(str).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;");
}
function linkify(str) {
return escHtml(str).replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer" class="creds-link">$1</a>');
}
function formatDuration(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = Math.floor(seconds % 60);
if (h > 0) return h + "h " + m + "m " + s + "s";
if (m > 0) return m + "m " + s + "s";
return s + "s";
}
// ── Fetch wrappers ────────────────────────────────────────────────
async function apiFetch(path, options) {
const res = await fetch(path, options || {});
if (!res.ok) {
let detail = res.status + " " + res.statusText;
try { const body = await res.json(); if (body && body.detail) detail = body.detail; } catch (e) {}
throw new Error(detail);
}
return res.json();
}
@@ -1,91 +0,0 @@
"use strict";
// ── Rebuild modal ─────────────────────────────────────────────────
function openRebuildModal() {
if (!$rebuildModal) return;
_rebuildLog = "";
_rebuildLogOffset = 0;
_rebuildServerDown = false;
_rebuildFinished = false;
if ($rebuildLog) { $rebuildLog.textContent = ""; $rebuildLog.style.display = "none"; }
var action = _rebuildIsEnabling ? "Enabling" : "Disabling";
var label = _rebuildFeatureName || "feature";
if ($rebuildStatus) $rebuildStatus.textContent = action + " " + label + "…";
if ($rebuildSpinner) $rebuildSpinner.classList.add("spinning");
if ($rebuildReboot) $rebuildReboot.style.display = "none";
if ($rebuildSave) $rebuildSave.style.display = "none";
if ($rebuildClose) $rebuildClose.disabled = true;
$rebuildModal.classList.add("open");
// Delay first poll slightly to let the rebuild service start and clear stale log
setTimeout(startRebuildPoll, 1500);
}
function closeRebuildModal() {
if ($rebuildModal) $rebuildModal.classList.remove("open");
stopRebuildPoll();
}
function appendRebuildLog(text) {
if (!text) return;
_rebuildLog += text;
// Log is collected silently for error reports — not displayed to user
}
function startRebuildPoll() {
pollRebuildStatus();
_rebuildPollTimer = setInterval(pollRebuildStatus, UPDATE_POLL_INTERVAL);
}
function stopRebuildPoll() {
if (_rebuildPollTimer) { clearInterval(_rebuildPollTimer); _rebuildPollTimer = null; }
}
async function pollRebuildStatus() {
if (_rebuildFinished) return;
try {
var data = await apiFetch("/api/rebuild/status?offset=" + _rebuildLogOffset);
if (_rebuildServerDown) { _rebuildServerDown = false; }
if (data.log) appendRebuildLog(data.log);
_rebuildLogOffset = data.offset;
if (data.running) return;
_rebuildFinished = true;
stopRebuildPoll();
if (data.result === "reboot_required") {
onRebuildDone("reboot_required");
} else {
onRebuildDone(data.result === "success");
}
} catch (err) {
if (!_rebuildServerDown) { _rebuildServerDown = true; if ($rebuildStatus) $rebuildStatus.textContent = "Applying changes…"; }
}
}
function onRebuildDone(result) {
if ($rebuildSpinner) $rebuildSpinner.classList.remove("spinning");
if ($rebuildClose) $rebuildClose.disabled = false;
if (result === true) {
if ($rebuildStatus) $rebuildStatus.textContent = "✓ Done";
// Auto-reload the page after a short delay so tiles and toggles reflect the new state
setTimeout(function() { window.location.reload(); }, 1200);
} else if (result === "reboot_required") {
if ($rebuildStatus) $rebuildStatus.textContent = "✓ Done — reboot required";
if ($rebuildReboot) $rebuildReboot.style.display = "inline-flex";
} else {
if ($rebuildStatus) $rebuildStatus.textContent = "✗ Something went wrong";
if ($rebuildSave) $rebuildSave.style.display = "inline-flex";
if ($rebuildReboot) $rebuildReboot.style.display = "inline-flex";
}
}
function saveRebuildErrorReport() {
var blob = new Blob([_rebuildLog], { type: "text/plain" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = "sovran-rebuild-error-" + new Date().toISOString().split(".")[0].replace(/:/g, "-") + ".txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
@@ -1,247 +0,0 @@
"use strict";
// ── Security Modal ────────────────────────────────────────────────
function openSecurityModal() {
if ($supportModal) $supportModal.classList.add("open");
var title = document.getElementById("support-modal-title");
if (title) title.textContent = "\uD83D\uDEE1 Security";
if ($supportBody) {
$supportBody.innerHTML =
// ── Section A: Security Reset ──────────────────────────────
'<div class="security-section">' +
'<h3 class="security-section-title">Security Reset</h3>' +
'<p class="security-section-desc">' +
'Run this if you are using this physical computer for the first time <strong>AND</strong> ' +
'it was not set up by you. This will complete the security setup by resetting all passwords ' +
'and your Bitcoin Lightning Node\u2019s private keys.' +
'</p>' +
'<p class="security-section-desc">' +
'You can also run this if you wish to reset all your passwords and your Bitcoin Lightning ' +
'Node\u2019s private keys. If you have not transferred the Bitcoin out of this node and did ' +
'not back up the private keys, <strong>you will lose your Bitcoin.</strong>' +
'</p>' +
'<button class="btn btn-primary" id="security-reset-open-btn">Proceed with Security Reset</button>' +
'<div id="security-reset-confirm" style="display:none;margin-top:16px;">' +
'<div class="security-warning-box">' +
'<p class="security-warning-text">' +
'<strong>\u26A0\uFE0F This will permanently delete:</strong>' +
'</p>' +
'<ul class="security-warning-list">' +
'<li>All generated passwords and SSH keys</li>' +
'<li>LND wallet data (seed words, channels, macaroons)</li>' +
'<li>Application databases</li>' +
'<li>Vaultwarden data</li>' +
'</ul>' +
'<p class="security-warning-text">You will go through onboarding again. <strong>This cannot be undone.</strong></p>' +
'</div>' +
'<div class="security-erase-group">' +
'<label class="security-erase-label" for="security-erase-input">Type <strong>ERASE</strong> to confirm:</label>' +
'<input class="security-erase-input" type="text" id="security-erase-input" autocomplete="off" placeholder="ERASE" />' +
'</div>' +
'<div class="security-reset-actions">' +
'<button class="btn btn-close-modal" id="security-reset-cancel-btn">Cancel</button>' +
'<button class="btn btn-danger" id="security-reset-confirm-btn" disabled>Erase &amp; Reset</button>' +
'</div>' +
'<div id="security-reset-status" class="security-status-msg"></div>' +
'</div>' +
'</div>' +
'<hr class="security-divider" />' +
// ── Section B: Verify System Integrity ────────────────────
'<div class="security-section">' +
'<h3 class="security-section-title">Verify System Integrity</h3>' +
'<p class="security-section-desc">' +
'Your Sovran_SystemsOS is built with NixOS \u2014 a system designed for complete transparency ' +
'and reproducibility. Every piece of software on this machine is built from publicly auditable ' +
'source code and verified using cryptographic hashes.' +
'</p>' +
'<p class="security-section-desc">This verification confirms three things:</p>' +
'<ol class="security-verify-list">' +
'<li>' +
'<strong>Source Code Match</strong> \u2014 The system configuration on this machine matches ' +
'the exact commit published in the public repository. No hidden changes were added.' +
'</li>' +
'<li>' +
'<strong>Binary Integrity</strong> \u2014 Every installed package in the system store is ' +
'verified against its expected cryptographic hash. If any binary, library, or config file ' +
'was tampered with, it will be detected.' +
'</li>' +
'<li>' +
'<strong>Running System Match</strong> \u2014 The currently running system matches what the ' +
'configuration says it should be. No unauthorized modifications are active.' +
'</li>' +
'</ol>' +
'<p class="security-section-desc">' +
'In short: if this verification passes, you can be confident that the software running on ' +
'your machine is exactly what is published \u2014 nothing more, nothing less.' +
'</p>' +
'<button class="btn btn-primary" id="security-verify-btn">Verify Now</button>' +
'<div id="security-verify-results" style="display:none;margin-top:16px;"></div>' +
'</div>';
// ── Wire Security Reset flow
var resetOpenBtn = document.getElementById("security-reset-open-btn");
var resetConfirmDiv = document.getElementById("security-reset-confirm");
var eraseInput = document.getElementById("security-erase-input");
var resetConfirmBtn = document.getElementById("security-reset-confirm-btn");
var resetCancelBtn = document.getElementById("security-reset-cancel-btn");
var resetStatus = document.getElementById("security-reset-status");
if (resetOpenBtn) {
resetOpenBtn.addEventListener("click", function() {
resetOpenBtn.style.display = "none";
if (resetConfirmDiv) resetConfirmDiv.style.display = "";
if (eraseInput) eraseInput.focus();
});
}
if (eraseInput && resetConfirmBtn) {
eraseInput.addEventListener("input", function() {
resetConfirmBtn.disabled = eraseInput.value.trim() !== "ERASE";
});
}
if (resetCancelBtn) {
resetCancelBtn.addEventListener("click", function() {
if (resetConfirmDiv) resetConfirmDiv.style.display = "none";
if (resetOpenBtn) resetOpenBtn.style.display = "";
if (eraseInput) eraseInput.value = "";
if (resetConfirmBtn) resetConfirmBtn.disabled = true;
if (resetStatus) { resetStatus.textContent = ""; resetStatus.className = "security-status-msg"; }
});
}
if (resetConfirmBtn) {
resetConfirmBtn.addEventListener("click", async function() {
if (!eraseInput || eraseInput.value.trim() !== "ERASE") return;
resetConfirmBtn.disabled = true;
resetConfirmBtn.textContent = "Erasing\u2026";
// Show the full-screen blocking overlay immediately so the user knows
// the wipe is in progress even while the API call runs synchronously.
var $secResetOverlay = document.getElementById("security-reset-overlay");
var $secResetStep = document.getElementById("security-reset-overlay-step");
if ($secResetOverlay) $secResetOverlay.classList.add("visible");
// Close the support modal so its content doesn't bleed through the overlay
if ($supportModal) $supportModal.classList.remove("open");
if (resetStatus) { resetStatus.textContent = "Running security reset\u2026"; resetStatus.className = "security-status-msg security-status-info"; }
try {
var data = await apiFetch("/api/security/reset", { method: "POST" });
// Switch to Phase 2: show the new password and wait for user confirmation
var phase1 = document.getElementById("security-reset-phase1");
var phase2 = document.getElementById("security-reset-phase2");
var passwordBox = document.getElementById("security-reset-new-password");
var rebootBtn = document.getElementById("security-reset-reboot-btn");
if (phase1) phase1.style.display = "none";
if (phase2) phase2.style.display = "";
if (passwordBox && data.new_password) passwordBox.textContent = data.new_password;
if (rebootBtn) {
// Keep button disabled for 5 seconds to prevent accidental clicks
var countdown = 5;
rebootBtn.textContent = "I have written down my new password \u2014 Reboot now (" + countdown + ")";
var timer = setInterval(function() {
countdown--;
if (countdown <= 0) {
clearInterval(timer);
rebootBtn.disabled = false;
rebootBtn.textContent = "I have written down my new password \u2014 Reboot now";
} else {
rebootBtn.textContent = "I have written down my new password \u2014 Reboot now (" + countdown + ")";
}
}, 1000);
rebootBtn.addEventListener("click", function() {
rebootBtn.disabled = true;
rebootBtn.textContent = "Rebooting\u2026";
if ($rebootOverlay) $rebootOverlay.classList.add("visible");
_rebootStartTime = Date.now();
_serverWentDown = false;
setTimeout(waitForServerReboot, REBOOT_INITIAL_DELAY);
var rebootCtrl = new AbortController();
setTimeout(function() { rebootCtrl.abort(); }, REBOOT_REQUEST_TIMEOUT);
fetch("/api/reboot", { method: "POST", signal: rebootCtrl.signal }).catch(function() {});
}, { once: true });
}
} catch (err) {
if ($secResetOverlay) $secResetOverlay.classList.remove("visible");
if (resetStatus) { resetStatus.textContent = "\u2717 Error: " + (err.message || "Reset failed."); resetStatus.className = "security-status-msg security-status-error"; }
resetConfirmBtn.disabled = false;
resetConfirmBtn.textContent = "Erase & Reset";
}
});
}
// ── Wire Verify System Integrity
var verifyBtn = document.getElementById("security-verify-btn");
var verifyResults = document.getElementById("security-verify-results");
if (verifyBtn && verifyResults) {
verifyBtn.addEventListener("click", async function() {
verifyBtn.disabled = true;
verifyBtn.textContent = "Verifying\u2026";
verifyResults.style.display = "";
verifyResults.innerHTML = '<p class="security-verify-loading">\u231B Running verification checks\u2026 This may take a few minutes.</p>';
try {
var data = await apiFetch("/api/security/verify-integrity", { method: "POST" });
var html = '<div class="security-verify-result-card">';
// Flake commit
html += '<div class="security-verify-row">';
html += '<span class="security-verify-label">Source Commit:</span>';
html += '<span class="security-verify-value security-verify-mono">' + escHtml(data.flake_commit || "unknown") + '</span>';
if (data.repo_url) {
html += '<a class="security-verify-link" href="' + escHtml(data.repo_url) + '" target="_blank" rel="noopener noreferrer">View on Gitea \u2197</a>';
}
html += '</div>';
// Store verification
var storeOk = data.store_verified === true;
html += '<div class="security-verify-row">';
html += '<span class="security-verify-label">Binary Integrity:</span>';
html += '<span class="security-verify-badge ' + (storeOk ? "security-verify-pass" : "security-verify-fail") + '">';
html += storeOk ? "\u2705 PASS" : "\u274C FAIL";
html += '</span>';
html += '</div>';
if (!storeOk && data.store_errors && data.store_errors.length > 0) {
html += '<details class="security-verify-errors"><summary>Show errors (' + data.store_errors.length + ')</summary>';
html += '<pre class="security-verify-pre">' + escHtml(data.store_errors.join("\n")) + '</pre>';
html += '</details>';
}
// System match
var sysOk = data.system_matches === true;
html += '<div class="security-verify-row">';
html += '<span class="security-verify-label">Running System Match:</span>';
html += '<span class="security-verify-badge ' + (sysOk ? "security-verify-pass" : "security-verify-fail") + '">';
html += sysOk ? "\u2705 PASS" : "\u274C FAIL";
html += '</span>';
html += '</div>';
if (!sysOk) {
html += '<div class="security-verify-path-row">';
html += '<span class="security-verify-path-label">Current:</span><code class="security-verify-mono">' + escHtml(data.current_system_path || "") + '</code>';
html += '</div>';
html += '<div class="security-verify-path-row">';
html += '<span class="security-verify-path-label">Expected:</span><code class="security-verify-mono">' + escHtml(data.expected_system_path || "") + '</code>';
html += '</div>';
}
html += '</div>';
verifyResults.innerHTML = html;
} catch (err) {
verifyResults.innerHTML = '<p class="security-status-msg security-status-error">\u274C Verification failed: ' + escHtml(err.message || "Unknown error") + '</p>';
}
verifyBtn.disabled = false;
verifyBtn.textContent = "Verify Now";
});
}
}
}
@@ -1,615 +0,0 @@
"use strict";
// ── Service detail modal ──────────────────────────────────────────
function _renderCredsHtml(credentials, unit) {
var html = "";
for (var i = 0; i < credentials.length; i++) {
var cred = credentials[i];
var id = "cred-" + Math.random().toString(36).substring(2, 8);
var qrBlock = "";
if (cred.qrcode) {
qrBlock = '<div class="creds-qr-wrap"><img class="creds-qr-img" src="' + cred.qrcode + '" alt="QR Code for ' + escHtml(cred.label) + '"><div class="creds-qr-hint">Scan with Zeus app on your phone</div></div>';
}
// If qronly, render the label + QR block only — skip value and copy button
if (cred.qronly) {
html += '<div class="creds-row"><div class="creds-label">' + escHtml(cred.label) + '</div>' + qrBlock + '</div>';
continue;
}
var displayValue = linkify(cred.value);
html += '<div class="creds-row"><div class="creds-label">' + escHtml(cred.label) + '</div>' + qrBlock + '<div class="creds-value-wrap"><div class="creds-value" id="' + id + '">' + displayValue + '</div><button class="creds-copy-btn" data-target="' + id + '">Copy</button></div></div>';
}
return html;
}
function _attachCopyHandlers(container) {
container.querySelectorAll(".creds-copy-btn").forEach(function(btn) {
btn.addEventListener("click", function() {
var target = document.getElementById(btn.dataset.target);
if (!target) return;
var text = target.textContent;
function onSuccess() {
btn.textContent = "Copied!";
btn.classList.add("copied");
setTimeout(function() { btn.textContent = "Copy"; btn.classList.remove("copied"); }, 1500);
}
function fallbackCopy() {
var ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
try {
document.execCommand("copy");
onSuccess();
} catch (e) {}
document.body.removeChild(ta);
}
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(onSuccess).catch(fallbackCopy);
} else {
fallbackCopy();
}
});
});
}
async function openServiceDetailModal(unit, name, icon) {
if (!$credsModal) return;
if ($credsTitle) {
$credsTitle.innerHTML = '';
if (icon) {
var iconImg = document.createElement("img");
iconImg.className = "creds-title-icon";
iconImg.src = "/static/icons/" + escHtml(icon) + ".svg";
iconImg.alt = name;
iconImg.onerror = function() { this.style.display = "none"; };
$credsTitle.appendChild(iconImg);
}
var nameSpan = document.createElement("span");
nameSpan.textContent = name;
$credsTitle.appendChild(nameSpan);
}
if ($credsBody) $credsBody.innerHTML = '<p class="creds-loading">Loading…</p>';
$credsModal.classList.add("open");
try {
var url = "/api/service-detail/" + encodeURIComponent(unit);
if (icon) url += "?icon=" + encodeURIComponent(icon);
var data = await apiFetch(url);
var html = "";
// Section A: Description
if (data.description) {
html += '<div class="svc-detail-section">' +
'<p class="svc-detail-desc">' + escHtml(data.description) + '</p>' +
'</div>';
}
// Section B: Status
// When a feature override is present, use the feature's enabled state so the
// modal matches what the dashboard tile shows (feature toggle is authoritative).
var effectiveEnabled = data.feature ? data.feature.enabled : data.enabled;
var effectiveHealth = data.feature && !data.feature.enabled
? "disabled"
: (data.health || data.status);
var sc = statusClass(effectiveHealth);
var st = statusText(effectiveHealth, effectiveEnabled);
html += '<div class="svc-detail-section">' +
'<div class="svc-detail-section-title">Status</div>' +
'<div class="svc-detail-status">' +
'<span class="status-dot ' + sc + '"></span>' +
'<span>' + escHtml(st) + '</span>' +
'</div>' +
'</div>';
// Section C: Domain diagnostics (domain services)
if (data.needs_domain) {
var steps = data.domain_check_steps || [];
var stepsHtml = "";
steps.forEach(function(step) {
var iconLabel = "—";
if (step.status === "ok") iconLabel = "✅";
else if (step.status === "error") iconLabel = "❌";
else if (step.status === "warning") iconLabel = "⚠️";
else if (step.status === "skipped") iconLabel = "⏭️";
var detail = escHtml(step.detail || "").replace(/\n/g, "<br>");
stepsHtml += '<div class="svc-detail-troubleshoot" style="margin-bottom:10px">' +
'<strong>' + iconLabel + ' Step ' + escHtml(String(step.step)) + ': ' + escHtml(step.label || "") + '</strong>' +
(detail ? '<div style="margin-top:6px">' + detail + '</div>' : '') +
'</div>';
});
var domainActionHtml = "";
var ds = data.domain_status || {};
if (!data.domain && data.domain_name) {
domainActionHtml = '<button class="btn btn-primary svc-detail-domain-btn" id="svc-detail-config-domain-btn">🌐 Configure Domain</button>';
} else if (data.domain && (ds.status === "dns_mismatch" || ds.status === "unresolvable")) {
domainActionHtml = '<button class="btn btn-primary svc-detail-domain-btn" id="svc-detail-reconfig-domain-btn">🔄 Reconfigure Domain</button>';
}
html += '<div class="svc-detail-section">' +
'<div class="svc-detail-section-title">Domain Diagnostic Checklist</div>' +
stepsHtml +
domainActionHtml +
'</div>';
if (unit === "livekit.service" && data.extra_ports && data.extra_ports.length > 0) {
var extraRows = "";
data.extra_ports.forEach(function(p) {
var statusIcon, statusClass2;
if (p.status === "listening") {
statusIcon = "✅ Open";
statusClass2 = "port-status-listening";
} else if (p.status === "firewall_open") {
statusIcon = "🟡 Firewall open";
statusClass2 = "port-status-open";
} else if (p.status === "closed") {
statusIcon = "❌ Closed";
statusClass2 = "port-status-closed";
} else {
statusIcon = "— Unknown";
statusClass2 = "port-status-unknown";
}
extraRows += '<tr>' +
'<td class="svc-detail-port-table-port">' + escHtml(p.port) + '</td>' +
'<td class="svc-detail-port-table-proto">' + escHtml(p.protocol) + '</td>' +
'<td class="svc-detail-port-table-desc">' + escHtml(p.description || "") + '</td>' +
'<td class="svc-detail-port-table-status ' + statusClass2 + '">' + statusIcon + '</td>' +
'</tr>';
});
html += '<div class="svc-detail-section">' +
'<div class="svc-detail-section-title">Step 4: Additional Ports</div>' +
'<table class="svc-detail-port-table">' +
'<thead><tr><th>Port</th><th>Protocol</th><th>Description</th><th>Status</th></tr></thead>' +
'<tbody>' + extraRows + '</tbody>' +
'</table>' +
'</div>';
}
} else if (data.port_statuses && data.port_statuses.length > 0) {
// Non-domain services (SSH) keep local single-port checks.
var portTableRows = "";
data.port_statuses.forEach(function(p) {
var statusIcon, statusClass2;
if (p.status === "listening") {
statusIcon = "✅ Open";
statusClass2 = "port-status-listening";
} else if (p.status === "firewall_open") {
statusIcon = "🟡 Firewall open";
statusClass2 = "port-status-open";
} else if (p.status === "closed") {
statusIcon = "🔴 Closed";
statusClass2 = "port-status-closed";
} else {
statusIcon = "— Unknown";
statusClass2 = "port-status-unknown";
}
portTableRows += '<tr>' +
'<td class="svc-detail-port-table-port">' + escHtml(p.port) + '</td>' +
'<td class="svc-detail-port-table-proto">' + escHtml(p.protocol) + '</td>' +
'<td class="svc-detail-port-table-desc">' + escHtml(p.description || "") + '</td>' +
'<td class="svc-detail-port-table-status ' + statusClass2 + '">' + statusIcon + '</td>' +
'</tr>';
});
html += '<div class="svc-detail-section">' +
'<div class="svc-detail-section-title">Port Status</div>' +
'<table class="svc-detail-port-table">' +
'<thead><tr><th>Port</th><th>Protocol</th><th>Description</th><th>Status</th></tr></thead>' +
'<tbody>' + portTableRows + '</tbody>' +
'</table>' +
'</div>';
}
// Section E: Credentials & Links
if (data.has_credentials && data.credentials && data.credentials.length > 0) {
html += '<div class="svc-detail-section">' +
'<div class="svc-detail-section-title">Credentials &amp; Access</div>' +
_renderCredsHtml(data.credentials, unit) +
(unit === "matrix-synapse.service" ?
'<hr class="matrix-actions-divider"><div class="matrix-actions-row">' +
'<button class="matrix-action-btn" id="matrix-add-user-btn"> Add New User</button>' +
'<button class="matrix-action-btn" id="matrix-change-pw-btn">🔑 Change Password</button>' +
'</div>' : "") +
(unit === "root-password-setup.service" ?
'<hr class="matrix-actions-divider"><div class="matrix-actions-row">' +
'<button class="matrix-action-btn" id="sys-change-pw-btn">🔑 Change Free Account Password</button>' +
'</div>' : "") +
'</div>';
} else if (!data.enabled && !data.feature) {
html += '<div class="svc-detail-section">' +
'<p class="creds-empty">This service is not enabled in your configuration.</p>' +
'</div>';
}
// Section F: Addon Feature toggle
if (data.feature) {
var feat = data.feature;
// Sync this feature into _featuresData so handleFeatureToggle can look up conflicts / ssl state
if (!_featuresData) {
_featuresData = { features: [feat], ssl_email_configured: false };
} else {
var fidx = _featuresData.features.findIndex(function(f) { return f.id === feat.id; });
if (fidx >= 0) { _featuresData.features[fidx] = feat; }
else { _featuresData.features.push(feat); }
}
var addonStatusLabel = feat.enabled ? "Enabled \u2713" : "Disabled";
var addonStatusCls = feat.enabled ? "addon-status--on" : "addon-status--off";
var addonBtnLabel = feat.enabled ? "Disable Feature" : "Enable Feature";
var addonBtnCls = feat.enabled ? "btn btn-close-modal" : "btn btn-primary";
// Section title: use a more specific label for mutually-exclusive Bitcoin node features
var addonSectionTitle = (feat.id === "bip110" || feat.id === "bitcoin-core")
? "\u20BF Bitcoin Node Selection"
: "\uD83D\uDD27 Addon Feature";
// Description: prefer the feature's own description over a generic fallback
var addonDesc = feat.description
? feat.description
: "This is an optional addon feature. You can enable or disable it at any time.";
// Conflicts warning: list mutually-exclusive feature names when present
var conflictsHtml = "";
if (feat.conflicts_with && feat.conflicts_with.length > 0) {
var conflictNames = feat.conflicts_with.map(function(cid) {
if (_featuresData && Array.isArray(_featuresData.features)) {
var cf = _featuresData.features.find(function(f) { return f.id === cid; });
if (cf) return cf.name;
}
return cid;
});
conflictsHtml = '<div class="feature-conflict-warning">\u26A0 Mutually exclusive with: ' + escHtml(conflictNames.join(", ")) + '</div>';
}
html += '<div class="svc-detail-section">' +
'<div class="svc-detail-section-title">' + addonSectionTitle + '</div>' +
'<p class="svc-detail-desc">' + escHtml(addonDesc) + '</p>' +
conflictsHtml +
'<div class="svc-detail-addon-row">' +
'<span class="svc-detail-addon-status ' + addonStatusCls + '">' + addonStatusLabel + '</span>' +
'<button class="' + addonBtnCls + '" id="svc-detail-addon-btn">' + escHtml(addonBtnLabel) + '</button>' +
'</div>' +
'</div>';
}
if ((effectiveEnabled || data.enabled) && unit !== "phpfpm-nextcloud.service" && unit !== "phpfpm-wordpress.service") {
html += '<div class="svc-detail-section svc-detail-restart-section">' +
'<div class="svc-detail-section-title">Troubleshooting</div>' +
'<p class="svc-detail-desc">If you\'re experiencing issues with this service, try restarting it.</p>' +
'<button class="btn btn-warning svc-detail-restart-btn" id="svc-detail-restart-btn">🔄 Restart Service</button>' +
'<div class="svc-detail-restart-result" id="svc-detail-restart-result"></div>' +
'</div>';
}
$credsBody.innerHTML = html;
_attachCopyHandlers($credsBody);
if (unit === "matrix-synapse.service") {
var addBtn = document.getElementById("matrix-add-user-btn");
var changePwBtn = document.getElementById("matrix-change-pw-btn");
if (addBtn) addBtn.addEventListener("click", function() { openMatrixCreateUserModal(unit, name, icon); });
if (changePwBtn) changePwBtn.addEventListener("click", function() { openMatrixChangePasswordModal(unit, name, icon); });
}
if (unit === "root-password-setup.service") {
var sysPwBtn = document.getElementById("sys-change-pw-btn");
if (sysPwBtn) sysPwBtn.addEventListener("click", function() { openSystemChangePasswordModal(unit, name, icon); });
}
if (data.feature) {
var addonBtn = document.getElementById("svc-detail-addon-btn");
if (addonBtn) {
var addonFeat = data.feature;
addonBtn.addEventListener("click", function() {
closeCredsModal();
handleFeatureToggle(addonFeat, !addonFeat.enabled);
});
}
}
var restartBtn = document.getElementById("svc-detail-restart-btn");
var restartResult = document.getElementById("svc-detail-restart-result");
if (restartBtn && restartResult) {
var RESTART_REFRESH_DELAY_MS = 3000;
restartBtn.addEventListener("click", async function() {
restartBtn.disabled = true;
restartBtn.textContent = "Restarting…";
restartResult.className = "svc-detail-restart-result";
restartResult.textContent = "";
try {
await apiFetch("/api/service/" + encodeURIComponent(unit) + "/restart", { method: "POST" });
restartResult.classList.add("success");
restartResult.textContent = "✅ Service restarted successfully.";
restartBtn.disabled = false;
restartBtn.textContent = "🔄 Restart Service";
setTimeout(function() {
openServiceDetailModal(unit, name, icon);
}, RESTART_REFRESH_DELAY_MS);
} catch (e) {
restartResult.classList.add("error");
restartResult.textContent = e && e.message ? e.message : "Failed to restart service. Please check service logs and try again.";
restartBtn.disabled = false;
restartBtn.textContent = "🔄 Restart Service";
}
});
}
// Configure / Reconfigure Domain buttons (for non-feature services that need a domain)
var configDomainBtn = document.getElementById("svc-detail-config-domain-btn");
var reconfigDomainBtn = document.getElementById("svc-detail-reconfig-domain-btn");
if ((configDomainBtn || reconfigDomainBtn) && data.needs_domain && data.domain_name) {
var pseudoFeat = {
id: data.domain_name,
name: name,
domain_name: data.domain_name,
needs_ddns: true,
extra_fields: []
};
if (configDomainBtn) configDomainBtn.addEventListener("click", function() {
closeCredsModal();
openDomainSetupModal(pseudoFeat, function() {
openServiceDetailModal(unit, name, icon);
});
});
if (reconfigDomainBtn) reconfigDomainBtn.addEventListener("click", function() {
closeCredsModal();
openDomainReconfigureModal(pseudoFeat, data.domain || "", function() {
openServiceDetailModal(unit, name, icon);
});
});
}
} catch (err) {
if ($credsBody) $credsBody.innerHTML = '<p class="creds-empty">Could not load service details.</p>';
}
}
// ── Credentials info modal ────────────────────────────────────────
async function openCredsModal(unit, name, icon) {
if (!$credsModal) return;
if ($credsTitle) {
$credsTitle.innerHTML = '';
if (icon) {
var iconImg = document.createElement("img");
iconImg.className = "creds-title-icon";
iconImg.src = "/static/icons/" + escHtml(icon) + ".svg";
iconImg.alt = name;
iconImg.onerror = function() { this.style.display = "none"; };
$credsTitle.appendChild(iconImg);
}
var nameSpan = document.createElement("span");
nameSpan.textContent = name + " — Connection Info";
$credsTitle.appendChild(nameSpan);
}
if ($credsBody) $credsBody.innerHTML = '<p class="creds-loading">Loading…</p>';
$credsModal.classList.add("open");
try {
var data = await apiFetch("/api/credentials/" + encodeURIComponent(unit));
if (!data.credentials || data.credentials.length === 0) {
$credsBody.innerHTML = '<p class="creds-empty">No connection info available yet.</p>';
return;
}
var html = _renderCredsHtml(data.credentials, unit);
if (unit === "matrix-synapse.service") {
html += '<hr class="matrix-actions-divider"><div class="matrix-actions-row">' +
'<button class="matrix-action-btn" id="matrix-add-user-btn"> Add New User</button>' +
'<button class="matrix-action-btn" id="matrix-change-pw-btn">🔑 Change Password</button>' +
'</div>';
}
$credsBody.innerHTML = html;
_attachCopyHandlers($credsBody);
if (unit === "matrix-synapse.service") {
var addBtn = document.getElementById("matrix-add-user-btn");
var changePwBtn = document.getElementById("matrix-change-pw-btn");
if (addBtn) addBtn.addEventListener("click", function() { openMatrixCreateUserModal(unit, name); });
if (changePwBtn) changePwBtn.addEventListener("click", function() { openMatrixChangePasswordModal(unit, name); });
}
} catch (err) {
$credsBody.innerHTML = '<p class="creds-empty">Could not load credentials.</p>';
}
}
function openMatrixCreateUserModal(unit, name, icon) {
if (!$credsBody) return;
$credsBody.innerHTML =
'<div class="matrix-form-group"><label class="matrix-form-label" for="matrix-new-username">Username</label>' +
'<input class="matrix-form-input" type="text" id="matrix-new-username" placeholder="alice" autocomplete="off"></div>' +
'<div class="matrix-form-group"><label class="matrix-form-label" for="matrix-new-password">Password</label>' +
'<input class="matrix-form-input" type="password" id="matrix-new-password" placeholder="Strong password" autocomplete="new-password"></div>' +
'<div class="matrix-form-checkbox-row"><input type="checkbox" id="matrix-new-admin"><label class="matrix-form-label" for="matrix-new-admin" style="margin:0">Make admin</label></div>' +
'<div class="matrix-form-actions">' +
'<button class="matrix-form-back" id="matrix-create-back-btn">← Back</button>' +
'<button class="matrix-form-submit" id="matrix-create-submit-btn">Create User</button>' +
'</div>' +
'<div class="matrix-form-result" id="matrix-create-result"></div>';
document.getElementById("matrix-create-back-btn").addEventListener("click", function() {
openServiceDetailModal(unit, name, icon);
});
document.getElementById("matrix-create-submit-btn").addEventListener("click", async function() {
var submitBtn = document.getElementById("matrix-create-submit-btn");
var resultEl = document.getElementById("matrix-create-result");
var username = (document.getElementById("matrix-new-username").value || "").trim();
var password = document.getElementById("matrix-new-password").value || "";
var isAdmin = document.getElementById("matrix-new-admin").checked;
if (!username || !password) {
resultEl.className = "matrix-form-result error";
resultEl.textContent = "Username and password are required.";
return;
}
submitBtn.disabled = true;
submitBtn.textContent = "Creating…";
resultEl.className = "matrix-form-result";
resultEl.textContent = "";
try {
var resp = await apiFetch("/api/matrix/create-user", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: username, password: password, admin: isAdmin })
});
resultEl.className = "matrix-form-result success";
resultEl.textContent = "✅ User @" + escHtml(resp.username) + " created successfully.";
submitBtn.textContent = "Create User";
submitBtn.disabled = false;
} catch (err) {
resultEl.className = "matrix-form-result error";
resultEl.textContent = "❌ " + (err.message || "Failed to create user.");
submitBtn.textContent = "Create User";
submitBtn.disabled = false;
}
});
}
function openMatrixChangePasswordModal(unit, name, icon) {
if (!$credsBody) return;
$credsBody.innerHTML =
'<div class="matrix-form-group"><label class="matrix-form-label" for="matrix-chpw-username">Username (localpart only, e.g. <em>alice</em>)</label>' +
'<input class="matrix-form-input" type="text" id="matrix-chpw-username" placeholder="alice" autocomplete="off"></div>' +
'<div class="matrix-form-group"><label class="matrix-form-label" for="matrix-chpw-password">New Password</label>' +
'<input class="matrix-form-input" type="password" id="matrix-chpw-password" placeholder="New strong password" autocomplete="new-password"></div>' +
'<div class="matrix-form-actions">' +
'<button class="matrix-form-back" id="matrix-chpw-back-btn">← Back</button>' +
'<button class="matrix-form-submit" id="matrix-chpw-submit-btn">Change Password</button>' +
'</div>' +
'<div class="matrix-form-result" id="matrix-chpw-result"></div>';
document.getElementById("matrix-chpw-back-btn").addEventListener("click", function() {
openServiceDetailModal(unit, name, icon);
});
document.getElementById("matrix-chpw-submit-btn").addEventListener("click", async function() {
var submitBtn = document.getElementById("matrix-chpw-submit-btn");
var resultEl = document.getElementById("matrix-chpw-result");
var username = (document.getElementById("matrix-chpw-username").value || "").trim();
var newPassword = document.getElementById("matrix-chpw-password").value || "";
if (!username || !newPassword) {
resultEl.className = "matrix-form-result error";
resultEl.textContent = "Username and new password are required.";
return;
}
submitBtn.disabled = true;
submitBtn.textContent = "Changing…";
resultEl.className = "matrix-form-result";
resultEl.textContent = "";
try {
var resp = await apiFetch("/api/matrix/change-password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username: username, new_password: newPassword })
});
resultEl.className = "matrix-form-result success";
resultEl.textContent = "✅ Password for @" + escHtml(resp.username) + " changed successfully.";
submitBtn.textContent = "Change Password";
submitBtn.disabled = false;
} catch (err) {
resultEl.className = "matrix-form-result error";
resultEl.textContent = "❌ " + (err.message || "Failed to change password.");
submitBtn.textContent = "Change Password";
submitBtn.disabled = false;
}
});
}
function openSystemChangePasswordModal(unit, name, icon) {
if (!$credsBody) return;
$credsBody.innerHTML =
'<div class="sys-chpw-header">' +
'<div class="sys-chpw-title">🔑 Change Free Account &amp; Hub Login Password</div>' +
'<div class="sys-chpw-desc">This updates the password for the <strong>free</strong> user account. <strong>This is also your Sovran Hub login password</strong> — both will change.</div>' +
'</div>' +
'<div class="matrix-form-group"><label class="matrix-form-label" for="sys-chpw-new">New Password</label>' +
'<div class="pw-input-wrap">' +
'<input class="matrix-form-input" type="password" id="sys-chpw-new" placeholder="New strong password" autocomplete="new-password">' +
'<button type="button" class="pw-toggle-btn" id="sys-chpw-new-toggle" aria-label="Toggle password visibility">👁</button>' +
'</div>' +
'<div class="pw-hint">Password must be at least 8 characters.</div></div>' +
'<div class="matrix-form-group"><label class="matrix-form-label" for="sys-chpw-confirm">Confirm Password</label>' +
'<div class="pw-input-wrap">' +
'<input class="matrix-form-input" type="password" id="sys-chpw-confirm" placeholder="Confirm new password" autocomplete="new-password">' +
'<button type="button" class="pw-toggle-btn" id="sys-chpw-confirm-toggle" aria-label="Toggle password visibility">👁</button>' +
'</div></div>' +
'<div class="pw-credentials-note">⚠ This will change both your desktop login and Hub login password. After changing, your updated password will appear in the System Passwords credentials tile.</div>' +
'<div class="matrix-form-actions">' +
'<button class="matrix-form-back" id="sys-chpw-back-btn">← Back</button>' +
'<button class="matrix-form-submit" id="sys-chpw-submit-btn">Change Password</button>' +
'</div>' +
'<div class="matrix-form-result" id="sys-chpw-result"></div>';
document.getElementById("sys-chpw-back-btn").addEventListener("click", function() {
openServiceDetailModal(unit, name, icon);
});
document.getElementById("sys-chpw-new-toggle").addEventListener("click", function() {
var inp = document.getElementById("sys-chpw-new");
var isHidden = inp.type === "password";
inp.type = isHidden ? "text" : "password";
this.textContent = isHidden ? "👁‍🗨" : "👁";
});
document.getElementById("sys-chpw-confirm-toggle").addEventListener("click", function() {
var inp = document.getElementById("sys-chpw-confirm");
var isHidden = inp.type === "password";
inp.type = isHidden ? "text" : "password";
this.textContent = isHidden ? "👁‍🗨" : "👁";
});
document.getElementById("sys-chpw-submit-btn").addEventListener("click", async function() {
var submitBtn = document.getElementById("sys-chpw-submit-btn");
var resultEl = document.getElementById("sys-chpw-result");
var newPassword = document.getElementById("sys-chpw-new").value || "";
var confirmPassword = document.getElementById("sys-chpw-confirm").value || "";
if (!newPassword || !confirmPassword) {
resultEl.className = "matrix-form-result error";
resultEl.textContent = "Both password fields are required.";
return;
}
if (newPassword.length < 8) {
resultEl.className = "matrix-form-result error";
resultEl.textContent = "Password must be at least 8 characters.";
return;
}
if (newPassword !== confirmPassword) {
resultEl.className = "matrix-form-result error";
resultEl.textContent = "Passwords do not match.";
return;
}
submitBtn.disabled = true;
submitBtn.textContent = "Changing…";
resultEl.className = "matrix-form-result";
resultEl.textContent = "";
try {
await apiFetch("/api/change-password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ new_password: newPassword, confirm_password: confirmPassword })
});
resultEl.className = "matrix-form-result success";
resultEl.textContent = "✅ Free account & Hub login password changed successfully.";
submitBtn.textContent = "Change Password";
submitBtn.disabled = false;
} catch (err) {
resultEl.className = "matrix-form-result error";
resultEl.textContent = "❌ " + (err.message || "Failed to change password.");
submitBtn.textContent = "Change Password";
submitBtn.disabled = false;
}
});
}
function closeCredsModal() { if ($credsModal) $credsModal.classList.remove("open"); }
-105
View File
@@ -1,105 +0,0 @@
"use strict";
// ── State ─────────────────────────────────────────────────────────
let _servicesCache = [];
let _categoryLabels = {};
let _updateLog = "";
let _updatePollTimer = null;
let _updateLogOffset = 0;
let _serverWasDown = false;
let _updateFinished = false;
let _supportTimerInt = null;
let _supportEnabledAt = null;
let _supportStatus = null; // last fetched /api/support/status payload
let _walletUnlockTimerInt = null;
let _cachedExternalIp = null;
// Current role (set during init from /api/config)
let _currentRole = "server_plus_desktop";
// Feature Manager state
let _featuresData = null;
let _rebuildLog = "";
let _rebuildLogOffset = 0;
let _rebuildPollTimer = null;
let _rebuildFinished = false;
let _rebuildServerDown = false;
let _pendingToggle = null; // {feature, extra} waiting for domain/confirm
let _rebuildFeatureName = "";
let _rebuildIsEnabling = true;
// ── DOM refs ──────────────────────────────────────────────────────
const $tilesArea = document.getElementById("tiles-area");
const $sidebarSupport = document.getElementById("sidebar-support");
const $sidebarFeatures = document.getElementById("sidebar-features");
// No longer needed — Update System moved to sidebar
// const $updateBtn = document.getElementById("btn-update");
// const $updateBadge = document.getElementById("update-badge");
const $internalIp = document.getElementById("ip-internal");
const $externalIp = document.getElementById("ip-external");
const $modal = document.getElementById("update-modal");
const $modalSpinner = document.getElementById("modal-spinner");
const $modalStatus = document.getElementById("modal-status");
const $modalLog = document.getElementById("modal-log");
const $btnReboot = document.getElementById("btn-reboot");
const $btnSave = document.getElementById("btn-save-report");
const $btnCloseModal = document.getElementById("btn-close-modal");
const $rebootOverlay = document.getElementById("reboot-overlay");
const $credsModal = document.getElementById("creds-modal");
const $credsTitle = document.getElementById("creds-modal-title");
const $credsBody = document.getElementById("creds-body");
const $credsCloseBtn = document.getElementById("creds-close-btn");
const $supportModal = document.getElementById("support-modal");
const $supportBody = document.getElementById("support-body");
const $supportCloseBtn = document.getElementById("support-close-btn");
const $logoutBtn = document.getElementById("btn-logout");
// Feature Manager — rebuild modal
const $rebuildModal = document.getElementById("rebuild-modal");
const $rebuildSpinner = document.getElementById("rebuild-spinner");
const $rebuildStatus = document.getElementById("rebuild-status");
const $rebuildLog = document.getElementById("rebuild-log");
const $rebuildReboot = document.getElementById("rebuild-reboot-btn");
const $rebuildSave = document.getElementById("rebuild-save-report");
const $rebuildClose = document.getElementById("rebuild-close-btn");
// Feature Manager — domain setup modal
const $domainSetupModal = document.getElementById("domain-setup-modal");
const $domainSetupTitle = document.getElementById("domain-setup-title");
const $domainSetupBody = document.getElementById("domain-setup-body");
const $domainSetupClose = document.getElementById("domain-setup-close-btn");
// Feature Manager — SSL email modal
const $sslEmailModal = document.getElementById("ssl-email-modal");
const $sslEmailInput = document.getElementById("ssl-email-input");
const $sslEmailSave = document.getElementById("ssl-email-save-btn");
const $sslEmailCancel = document.getElementById("ssl-email-cancel-btn");
const $sslEmailClose = document.getElementById("ssl-email-close-btn");
// Feature Manager — confirm modal
const $featureConfirmModal = document.getElementById("feature-confirm-modal");
const $featureConfirmMsg = document.getElementById("feature-confirm-message");
const $featureConfirmOk = document.getElementById("feature-confirm-ok-btn");
const $featureConfirmCancel = document.getElementById("feature-confirm-cancel-btn");
const $featureConfirmClose = document.getElementById("feature-confirm-close-btn");
// Port Requirements modal
const $portReqModal = document.getElementById("port-requirements-modal");
const $portReqBody = document.getElementById("port-req-body");
const $portReqClose = document.getElementById("port-req-close-btn");
// Upgrade modal (Node → Server+Desktop)
const $upgradeModal = document.getElementById("upgrade-modal");
const $upgradeConfirmBtn = document.getElementById("upgrade-confirm-btn");
const $upgradeCancelBtn = document.getElementById("upgrade-cancel-btn");
const $upgradeCloseBtn = document.getElementById("upgrade-close-btn");
// System status banner
// (removed — health is now shown per-tile via the composite health field)
@@ -1,630 +0,0 @@
"use strict";
// ── Tech Support modal ────────────────────────────────────────────
async function openSupportModal() {
if (!$supportModal) return;
$supportModal.classList.add("open");
$supportBody.innerHTML = '<p class="creds-loading">Checking support status…</p>';
try {
var status = await apiFetch("/api/support/status");
_supportStatus = status;
if (status.active) { _supportEnabledAt = status.enabled_at; renderSupportActive(status); }
else if (!status.sshd_enabled) { renderSupportSshdOff(); }
else { renderSupportInactive(); }
} catch (err) {
$supportBody.innerHTML = '<p class="creds-empty">Could not check support status.</p>';
}
}
function renderSupportSshdOff() {
stopSupportTimer();
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">🛟</div>',
'<h3 class="support-heading">Need help from Sovran Systems?</h3>',
'<p class="support-desc">To get Tech Support, SSH must be enabled first. SSH is <strong>off by default</strong> for maximum security — it only needs to be on during a support session.</p>',
'<div class="support-wallet-box support-wallet-protected">',
'<div class="support-wallet-header"><span class="support-wallet-icon">🔐</span><span class="support-wallet-title">SSH is Off</span></div>',
'<p class="support-wallet-desc">SSH (remote login) is <strong>disabled by default</strong> on your Sovran Pro. Clicking the button below will enable SSH and trigger a system rebuild. Once complete, you can then grant support access.</p>',
'<p class="support-wallet-desc">When you end the support session, you\'ll be able to disable SSH to return to the default secure state.</p>',
'</div>',
'<div class="support-steps"><div class="support-steps-title">Steps:</div><ol>',
'<li>Enable SSH (triggers a system rebuild — takes a few minutes)</li>',
'<li>Grant Sovran Systems temporary support access</li>',
'<li>End the session when done — you\'ll be prompted to disable SSH</li>',
'</ol></div>',
'<button class="btn support-btn-enable" id="btn-sshd-enable">Enable SSH</button>',
'<p class="support-fine-print">This will trigger a NixOS rebuild. Your machine will remain operational during the rebuild.</p>',
'</div>',
].join("");
document.getElementById("btn-sshd-enable").addEventListener("click", enableSshd);
}
async function enableSshd() {
var btn = document.getElementById("btn-sshd-enable");
if (btn) { btn.disabled = true; btn.textContent = "Enabling SSH…"; }
try {
await apiFetch("/api/features/toggle", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ feature: "sshd", enabled: true }),
});
// Poll until rebuild completes and sshd_enabled is true
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">⚙️</div>',
'<h3 class="support-heading">Enabling SSH…</h3>',
'<p class="support-desc">A system rebuild is in progress. This may take a few minutes. The page will update automatically when SSH is ready.</p>',
'<p class="creds-loading" id="sshd-rebuild-status">Rebuilding system…</p>',
'</div>',
].join("");
pollForSshdReady();
} catch (err) {
if (btn) { btn.disabled = false; btn.textContent = "Enable SSH"; }
alert("Failed to enable SSH. Please try again.");
}
}
function pollForSshdReady() {
var attempts = 0;
var maxAttempts = 60; // 5 minutes (5s interval)
var interval = setInterval(async function() {
attempts++;
try {
var status = await apiFetch("/api/support/status");
var el = document.getElementById("sshd-rebuild-status");
if (status.sshd_enabled) {
clearInterval(interval);
_supportStatus = status;
renderSupportInactive();
} else if (attempts >= maxAttempts) {
clearInterval(interval);
if (el) el.textContent = "Rebuild is taking longer than expected. Please close this dialog and try again.";
} else {
if (el) el.textContent = "Rebuilding system… (" + attempts * 5 + "s)";
}
} catch (_) {}
}, 5000);
}
function renderSupportInactive() {
stopSupportTimer();
var ip = _cachedExternalIp || "loading…";
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">🛟</div>',
'<h3 class="support-heading">Need help from Sovran Systems?</h3>',
'<p class="support-desc">This will temporarily grant our support team SSH access to your machine so we can help diagnose and fix issues.</p>',
'<div class="support-wallet-box support-wallet-protected">',
'<div class="support-wallet-header"><span class="support-wallet-icon">✅</span><span class="support-wallet-title">SSH is Active</span></div>',
'<p class="support-wallet-desc">SSH is enabled on your machine. You can now grant Sovran Systems temporary access below.</p>',
'</div>',
'<div class="support-info-box">',
'<div class="support-info-row"><span class="support-info-label">Your IP</span><span class="support-info-value">' + escHtml(ip) + '</span></div>',
'<div class="support-info-hint">This IP will be shared with Sovran Systems support</div>',
'</div>',
'<div class="support-wallet-box support-wallet-protected">',
'<div class="support-wallet-header"><span class="support-wallet-icon">🔒</span><span class="support-wallet-title">Wallet Protection</span></div>',
'<p class="support-wallet-desc">Wallet files (LND, Sparrow, Bisq) are <strong>protected by default</strong>. Support staff cannot access your private keys unless you explicitly grant access.</p>',
'</div>',
'<div class="support-steps"><div class="support-steps-title">What happens:</div><ol>',
'<li>A restricted <code>sovran-support</code> user is created with limited access</li>',
'<li>Our SSH key is added only to that restricted account</li>',
'<li>Wallet files are locked via access controls — not visible to support</li>',
'<li>You control if and when wallet access is granted (time-limited)</li>',
'<li>All session events are logged for your audit</li>',
'</ol></div>',
'<button class="btn support-btn-enable" id="btn-support-enable">Enable Support Access</button>',
'<p class="support-fine-print">You can revoke access at any time. When you end the session, you\'ll be able to disable SSH to return to the default secure state.</p>',
'</div>',
].join("");
document.getElementById("btn-support-enable").addEventListener("click", enableSupport);
}
function renderSupportActive(status) {
var ip = _cachedExternalIp || "loading…";
var walletProtected = status && status.wallet_protected;
var walletUnlocked = status && status.wallet_unlocked;
var unlockUntil = status && status.wallet_unlocked_until_human ? status.wallet_unlocked_until_human : "";
var protectedPaths = (status && status.protected_paths && status.protected_paths.length)
? status.protected_paths : [];
var walletSection;
if (walletProtected) {
if (walletUnlocked) {
walletSection = [
'<div class="support-wallet-box support-wallet-unlocked">',
'<div class="support-wallet-header"><span class="support-wallet-icon">🔓</span><span class="support-wallet-title">Wallet Access: UNLOCKED</span></div>',
'<p class="support-wallet-desc">You have granted support temporary access to wallet files' + (unlockUntil ? ' until <strong>' + escHtml(unlockUntil) + '</strong>' : '') + '.</p>',
'<button class="btn support-btn-wallet-lock" id="btn-wallet-lock">Re-lock Wallet Now</button>',
'</div>',
].join("");
} else {
var pathList = protectedPaths.length
? '<ul class="support-wallet-paths">' + protectedPaths.map(function(p){ return '<li>' + escHtml(p) + '</li>'; }).join("") + '</ul>'
: '';
walletSection = [
'<div class="support-wallet-box support-wallet-protected">',
'<div class="support-wallet-header"><span class="support-wallet-icon">🔒</span><span class="support-wallet-title">Wallet Files: Protected</span></div>',
'<p class="support-wallet-desc">Support cannot access your wallet files. Grant temporary access only if needed for wallet troubleshooting.</p>',
pathList,
'<div class="support-wallet-unlock-row">',
'<select id="wallet-unlock-duration" class="support-unlock-select">',
'<option value="3600">1 hour</option>',
'<option value="1800">30 minutes</option>',
'<option value="7200">2 hours</option>',
'</select>',
'<button class="btn support-btn-wallet-unlock" id="btn-wallet-unlock">Grant Wallet Access</button>',
'</div>',
'</div>',
].join("");
}
} else {
walletSection = [
'<div class="support-wallet-box support-wallet-warning">',
'<div class="support-wallet-header"><span class="support-wallet-icon">⚠️</span><span class="support-wallet-title">Wallet Protection Unavailable</span></div>',
'<p class="support-wallet-desc">The restricted support user could not be created. Support is running with root access — wallet files may be accessible. End the session if you are concerned.</p>',
'</div>',
].join("");
}
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big support-active-icon">🔓</div>',
'<h3 class="support-heading support-active-heading">Support Access is Active</h3>',
'<p class="support-active-note">Sovran Systems can currently connect to your machine via SSH.</p>',
'<div class="support-info-box support-active-box">',
'<div class="support-info-row"><span class="support-info-label">Your IP</span><span class="support-info-value">' + escHtml(ip) + '</span></div>',
'<div class="support-info-row"><span class="support-info-label">Duration</span><span class="support-info-value" id="support-timer">…</span></div>',
'</div>',
walletSection,
'<button class="btn support-btn-disable" id="btn-support-disable">End Support Session</button>',
'<p class="support-fine-print">This will remove the SSH key and revoke all wallet access immediately.</p>',
'<button class="btn support-btn-auditlog" id="btn-support-audit">View Audit Log</button>',
'</div>',
'<div id="support-audit-container" class="support-audit-container" style="display:none;"></div>',
].join("");
document.getElementById("btn-support-disable").addEventListener("click", disableSupport);
document.getElementById("btn-support-audit").addEventListener("click", toggleAuditLog);
if (walletProtected && !walletUnlocked) {
document.getElementById("btn-wallet-unlock").addEventListener("click", walletUnlock);
}
if (walletProtected && walletUnlocked) {
document.getElementById("btn-wallet-lock").addEventListener("click", walletLock);
}
startSupportTimer();
if (walletUnlocked && status.wallet_unlocked_until) {
startWalletUnlockTimer(status.wallet_unlocked_until);
}
}
function renderSupportRemoved(verified) {
stopSupportTimer();
stopWalletUnlockTimer();
var icon = verified ? "✅" : "⚠️";
var msg = verified ? "The Sovran Systems SSH key has been completely removed from your machine. We no longer have any access." : "The key removal was requested but could not be fully verified. Please reboot to ensure it is gone.";
var vclass = verified ? "verified-gone" : "verify-warning";
var vlabel = verified ? "✓ Removed — No access" : "⚠ Verify by rebooting";
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">' + icon + '</div>',
'<h3 class="support-heading">Support Session Ended</h3>',
'<p class="support-desc">' + escHtml(msg) + '</p>',
'<div class="support-verify-box"><span class="support-verify-label">SSH Key Status:</span><span class="support-verify-value ' + vclass + '">' + vlabel + '</span></div>',
'<div class="support-wallet-box support-wallet-protected" style="margin-top:12px;">',
'<div class="support-wallet-header"><span class="support-wallet-icon">🔐</span><span class="support-wallet-title">Disable SSH When Done</span></div>',
'<p class="support-wallet-desc">SSH is still enabled on your machine. Click below to turn it off and return to the default secure state.</p>',
'<button class="btn support-btn-enable" id="btn-sshd-disable">Disable SSH</button>',
'</div>',
'<button class="btn support-btn-done" id="btn-support-done">Done</button>',
'</div>',
].join("");
document.getElementById("btn-support-done").addEventListener("click", closeSupportModal);
document.getElementById("btn-sshd-disable").addEventListener("click", disableSshd);
}
async function enableSupport() {
var btn = document.getElementById("btn-support-enable");
if (btn) { btn.disabled = true; btn.textContent = "Enabling…"; }
try {
await apiFetch("/api/support/enable", { method: "POST" });
var status = await apiFetch("/api/support/status");
_supportStatus = status;
_supportEnabledAt = status.enabled_at;
renderSupportActive(status);
} catch (err) {
if (btn) { btn.disabled = false; btn.textContent = "Enable Support Access"; }
alert("Failed to enable support access. Please try again.");
}
}
async function disableSupport() {
var btn = document.getElementById("btn-support-disable");
if (btn) { btn.disabled = true; btn.textContent = "Removing key…"; }
try {
var result = await apiFetch("/api/support/disable", { method: "POST" });
renderSupportRemoved(result.verified);
} catch (err) {
if (btn) { btn.disabled = false; btn.textContent = "End Support Session"; }
alert("Failed to disable support access. Please try again.");
}
}
async function disableSshd() {
var btn = document.getElementById("btn-sshd-disable");
if (btn) { btn.disabled = true; btn.textContent = "Disabling SSH…"; }
try {
await apiFetch("/api/features/toggle", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ feature: "sshd", enabled: false }),
});
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">⚙️</div>',
'<h3 class="support-heading">Disabling SSH…</h3>',
'<p class="support-desc">A system rebuild is in progress to turn off SSH. This may take a few minutes.</p>',
'<p class="creds-loading" id="sshd-disable-status">Rebuilding system…</p>',
'</div>',
].join("");
pollForSshdDisabled();
} catch (err) {
if (btn) { btn.disabled = false; btn.textContent = "Disable SSH"; }
alert("Failed to disable SSH. Please try again.");
}
}
function pollForSshdDisabled() {
var attempts = 0;
var maxAttempts = 60; // 5 minutes (5s interval)
var interval = setInterval(async function() {
attempts++;
try {
var status = await apiFetch("/api/support/status");
var el = document.getElementById("sshd-disable-status");
if (!status.sshd_enabled) {
clearInterval(interval);
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">🔐</div>',
'<h3 class="support-heading">SSH is Off</h3>',
'<p class="support-desc">SSH has been disabled. Your machine is back to its default secure state.</p>',
'<button class="btn support-btn-done" id="btn-support-done">Done</button>',
'</div>',
].join("");
document.getElementById("btn-support-done").addEventListener("click", closeSupportModal);
} else if (attempts >= maxAttempts) {
clearInterval(interval);
if (el) el.textContent = "Rebuild is taking longer than expected. Please close this dialog and try again.";
} else {
if (el) el.textContent = "Rebuilding system… (" + attempts * 5 + "s)";
}
} catch (_) {}
}, 5000);
}
async function walletUnlock() {
var btn = document.getElementById("btn-wallet-unlock");
var sel = document.getElementById("wallet-unlock-duration");
var duration = sel ? parseInt(sel.value, 10) : 3600;
if (btn) { btn.disabled = true; btn.textContent = "Unlocking…"; }
try {
var result = await apiFetch("/api/support/wallet-unlock", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ duration: duration }),
});
var status = await apiFetch("/api/support/status");
_supportStatus = status;
renderSupportActive(status);
} catch (err) {
if (btn) { btn.disabled = false; btn.textContent = "Grant Wallet Access"; }
alert("Failed to unlock wallet access: " + (err.message || "Unknown error"));
}
}
async function walletLock() {
var btn = document.getElementById("btn-wallet-lock");
if (btn) { btn.disabled = true; btn.textContent = "Locking…"; }
try {
await apiFetch("/api/support/wallet-lock", { method: "POST" });
var status = await apiFetch("/api/support/status");
_supportStatus = status;
renderSupportActive(status);
} catch (err) {
if (btn) { btn.disabled = false; btn.textContent = "Re-lock Wallet Now"; }
alert("Failed to re-lock wallet: " + (err.message || "Unknown error"));
}
}
async function toggleAuditLog() {
var container = document.getElementById("support-audit-container");
if (!container) return;
if (container.style.display !== "none") {
container.style.display = "none";
return;
}
container.style.display = "block";
container.innerHTML = '<p class="creds-loading">Loading audit log…</p>';
try {
var data = await apiFetch("/api/support/audit-log");
if (!data.entries || data.entries.length === 0) {
container.innerHTML = '<p class="support-audit-empty">No audit events recorded yet.</p>';
} else {
container.innerHTML = '<div class="support-audit-log">' +
data.entries.map(function(e) { return '<div class="support-audit-entry">' + escHtml(e) + '</div>'; }).join("") +
'</div>';
}
} catch (err) {
container.innerHTML = '<p class="creds-empty">Could not load audit log.</p>';
}
}
function startSupportTimer() {
stopSupportTimer();
updateSupportTimer();
_supportTimerInt = setInterval(updateSupportTimer, SUPPORT_TIMER_INTERVAL);
}
function stopSupportTimer() {
if (_supportTimerInt) { clearInterval(_supportTimerInt); _supportTimerInt = null; }
}
function updateSupportTimer() {
var el = document.getElementById("support-timer");
if (!el || !_supportEnabledAt) return;
var elapsed = (Date.now() / 1000) - _supportEnabledAt;
el.textContent = formatDuration(Math.max(0, elapsed));
}
function startWalletUnlockTimer(expiresAt) {
stopWalletUnlockTimer();
_walletUnlockTimerInt = setInterval(function() {
if (Date.now() / 1000 >= expiresAt) {
stopWalletUnlockTimer();
// Refresh the support modal to show re-locked state
apiFetch("/api/support/status").then(function(status) {
_supportStatus = status;
renderSupportActive(status);
}).catch(function() {});
}
}, 10000);
}
function stopWalletUnlockTimer() {
if (_walletUnlockTimerInt) { clearInterval(_walletUnlockTimerInt); _walletUnlockTimerInt = null; }
}
function closeSupportModal() {
if ($supportModal) $supportModal.classList.remove("open");
stopSupportTimer();
stopWalletUnlockTimer();
}
// ── Manual Backup modal ───────────────────────────────────────────
var _backupPollTimer = null;
var _backupLogOffset = 0;
function openBackupModal() {
if (!$supportModal) return;
$supportModal.classList.add("open");
$supportBody.innerHTML = '<p class="creds-loading">Detecting external drives\u2026</p>';
detectDrivesAndRender();
}
async function detectDrivesAndRender() {
try {
// Check whether a backup is already in progress
var status = await apiFetch("/api/backup/status?offset=0");
if (status.running) {
renderBackupRunning();
_backupLogOffset = status.offset || 0;
if (status.log) {
var logDiv = document.getElementById("backup-log");
if (logDiv) { logDiv.insertAdjacentText("beforeend", status.log); logDiv.scrollTop = logDiv.scrollHeight; }
}
startBackupPoll();
return;
}
} catch (_) {}
try {
var data = await apiFetch("/api/backup/drives");
renderBackupReady(data.drives || []);
} catch (err) {
$supportBody.innerHTML = '<p class="creds-empty">Could not detect drives. Please try again.</p>';
}
}
function renderBackupReady(drives) {
var driveSelector = "";
if (drives.length > 0) {
driveSelector = [
'<label class="support-info-label" style="display:block;margin-bottom:6px;">Select drive:</label>',
'<div style="display:flex;gap:8px;align-items:center;margin-bottom:14px;">',
'<select id="backup-drive-select" class="support-unlock-select" style="flex:1;">',
].join("");
for (var i = 0; i < drives.length; i++) {
var d = drives[i];
driveSelector += '<option value="' + escHtml(d.path) + '">' +
escHtml(d.name) + ' \u2014 ' + d.free_gb + ' GB free / ' + d.total_gb + ' GB total' +
'</option>';
}
driveSelector += '</select>';
driveSelector += '<button class="btn support-btn-auditlog" id="btn-backup-refresh" style="white-space:nowrap;">&#x21bb; Refresh</button>';
driveSelector += '</div>';
driveSelector += '<button class="btn support-btn-enable" id="btn-start-backup">Start Backup</button>';
} else {
driveSelector = [
'<div class="support-wallet-box support-wallet-warning">',
'<div class="support-wallet-header">',
'<span class="support-wallet-icon">\u26a0\ufe0f</span>',
'<span class="support-wallet-title">No External Drive Detected</span>',
'</div>',
'<p class="support-wallet-desc">',
'No USB drive was found under /run/media/. ',
'Make sure the drive is plugged in and mounted, then click Refresh.',
'</p>',
'</div>',
'<button class="btn support-btn-auditlog" id="btn-backup-refresh">&#x21bb; Refresh</button>',
].join("");
}
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">\ud83d\udcbe</div>',
'<h3 class="support-heading">Manual Backup</h3>',
'<div class="support-wallet-box support-wallet-protected" style="margin-bottom:16px;">',
'<p class="support-wallet-desc">',
'Your Sovran Pro already backs up your data automatically to its internal second drive. ',
'This manual backup lets you create an additional copy on an external USB drive \u2014 ',
'storing your data in a third location, outside the computer, for maximum protection ',
'against hardware failure or physical damage.',
'</p>',
'</div>',
'<div class="support-steps">',
'<div class="support-steps-title">Requirements</div>',
'<ol class="support-backup-steps">',
'<li>USB hard drive plugged into one of the open USB ports on your Sovran Pro</li>',
'<li>At least 500 GB of free space on the drive</li>',
'<li>Drive must be formatted as <strong>exFAT</strong></li>',
'</ol>',
'</div>',
'<div class="support-steps">',
'<div class="support-steps-title">What gets backed up</div>',
'<ol class="support-backup-steps">',
'<li>NixOS configuration (<code>/etc/nixos</code>)</li>',
'<li>nix-bitcoin secrets (<code>/etc/nix-bitcoin-secrets</code>)</li>',
'<li>System service data (<code>/var/lib</code>) including Vaultwarden, bitcoind, LND, sovran-hub, domains, and secrets</li>',
'<li>Home directory (<code>/home</code>)</li>',
'</ol>',
'</div>',
'<div class="support-wallet-box support-wallet-warning">',
'<div class="support-wallet-header">',
'<span class="support-wallet-icon">\u23f1\ufe0f</span>',
'<span class="support-wallet-title">Time Estimate</span>',
'</div>',
'<p class="support-wallet-desc">This backup can take <strong>up to 4 hours</strong> depending on the amount of data stored on your Sovran Pro and the speed of your external hard drive. Be patient\u2026</p>',
'</div>',
driveSelector,
'</div>',
].join("");
if (drives.length > 0) {
document.getElementById("btn-start-backup").addEventListener("click", startBackup);
document.getElementById("btn-backup-refresh").addEventListener("click", function() {
$supportBody.innerHTML = '<p class="creds-loading">Scanning for external drives\u2026</p>';
detectDrivesAndRender();
});
} else {
document.getElementById("btn-backup-refresh").addEventListener("click", function() {
$supportBody.innerHTML = '<p class="creds-loading">Scanning for external drives\u2026</p>';
detectDrivesAndRender();
});
}
}
async function startBackup() {
var btn = document.getElementById("btn-start-backup");
if (btn) { btn.disabled = true; btn.textContent = "Starting\u2026"; }
var sel = document.getElementById("backup-drive-select");
var target = sel ? sel.value : "";
try {
_backupLogOffset = 0;
await apiFetch("/api/backup/run" + (target ? "?target=" + encodeURIComponent(target) : ""), { method: "POST" });
renderBackupRunning();
startBackupPoll();
} catch (err) {
if (btn) { btn.disabled = false; btn.textContent = "Start Backup"; }
alert("Failed to start backup: " + (err.message || "Unknown error"));
}
}
function renderBackupRunning() {
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big support-active-icon">\ud83d\udcbe</div>',
'<h3 class="support-heading support-active-heading">Backup In Progress</h3>',
'<div class="support-wallet-box support-wallet-warning">',
'<div class="support-wallet-header">',
'<span class="support-wallet-icon">\u26a0\ufe0f</span>',
'<span class="support-wallet-title">Do Not Unplug</span>',
'</div>',
'<p class="support-wallet-desc">Do not remove the USB drive while the backup is running. This could corrupt the backup and your drive.</p>',
'</div>',
'<div class="modal-log" id="backup-log" style="text-align:left;"></div>',
'</div>',
].join("");
}
function startBackupPoll() {
stopBackupPoll();
_backupPollTimer = setInterval(pollBackupStatus, 2000);
pollBackupStatus();
}
function stopBackupPoll() {
if (_backupPollTimer) { clearInterval(_backupPollTimer); _backupPollTimer = null; }
}
async function pollBackupStatus() {
try {
var data = await apiFetch("/api/backup/status?offset=" + _backupLogOffset);
var logDiv = document.getElementById("backup-log");
if (logDiv && data.log) {
logDiv.insertAdjacentText("beforeend", data.log);
logDiv.scrollTop = logDiv.scrollHeight;
}
_backupLogOffset = data.offset;
if (!data.running) {
stopBackupPoll();
renderBackupDone(data.result === "success");
}
} catch (_) {}
}
function renderBackupDone(success) {
var logDiv = document.getElementById("backup-log");
var logContent = logDiv ? logDiv.textContent : "";
if (success) {
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">\u2705</div>',
'<h3 class="support-heading">All Finished!</h3>',
'<div class="support-wallet-box support-wallet-protected">',
'<div class="support-wallet-header">',
'<span class="support-wallet-icon">\u23cf\ufe0f</span>',
'<span class="support-wallet-title">Eject Your Drive</span>',
'</div>',
'<p class="support-wallet-desc">Please eject the drive before removing it from your Sovran Pro.</p>',
'</div>',
'<div class="modal-log" id="backup-log-done" style="text-align:left;"></div>',
'<button class="btn support-btn-done" id="btn-backup-close">Close</button>',
'</div>',
].join("");
var doneLog = document.getElementById("backup-log-done");
if (doneLog) { doneLog.textContent = logContent; doneLog.scrollTop = doneLog.scrollHeight; }
} else {
$supportBody.innerHTML = [
'<div class="support-section">',
'<div class="support-icon-big">\u26a0\ufe0f</div>',
'<h3 class="support-heading">Backup Failed</h3>',
'<p class="support-desc">The backup did not complete successfully. Please check that the USB drive is still connected, has enough free space, and is formatted as exFAT. Then try again.</p>',
'<div class="modal-log" id="backup-log-fail" style="text-align:left;"></div>',
'<button class="btn support-btn-done" id="btn-backup-close">Close</button>',
'</div>',
].join("");
var failLog = document.getElementById("backup-log-fail");
if (failLog) { failLog.textContent = logContent; failLog.scrollTop = failLog.scrollHeight; }
}
document.getElementById("btn-backup-close").addEventListener("click", closeSupportModal);
}
-318
View File
@@ -1,318 +0,0 @@
"use strict";
// ── Bitcoin IBD sync state (for ETA calculation) ──────────────────
// Keyed by tileId: { progress: float, timestamp: ms }
var _btcSyncPrev = {};
// ── Render: initial build ─────────────────────────────────────────
function buildTiles(services, categoryLabels) {
_servicesCache = services;
var grouped = {};
var supportServices = [];
for (var i = 0; i < services.length; i++) {
var svc = services[i];
// Support tiles go to the sidebar, not the main grid
if (svc.category === "support" || svc.type === "support") {
supportServices.push(svc);
continue;
}
var cat = svc.category || "other";
if (!grouped[cat]) grouped[cat] = [];
grouped[cat].push(svc);
}
renderSidebarSupport(supportServices);
$tilesArea.innerHTML = "";
var orderedKeys = CATEGORY_ORDER.filter(function(k) { return grouped[k]; });
Object.keys(grouped).forEach(function(k) {
if (orderedKeys.indexOf(k) === -1) orderedKeys.push(k);
});
for (var j = 0; j < orderedKeys.length; j++) {
var catKey = orderedKeys[j];
var entries = grouped[catKey];
if (!entries || entries.length === 0) continue;
var label = categoryLabels[catKey] || catKey;
var section = document.createElement("div");
section.className = "category-section";
section.dataset.category = catKey;
section.innerHTML = '<div class="section-header">' + escHtml(label) + '</div><hr class="section-divider" /><div class="tiles-grid" data-cat="' + escHtml(catKey) + '"></div>';
var grid = section.querySelector(".tiles-grid");
for (var k = 0; k < entries.length; k++) {
grid.appendChild(buildTile(entries[k]));
}
$tilesArea.appendChild(section);
}
if ($tilesArea.children.length === 0) {
$tilesArea.innerHTML = '<div class="empty-state"><p>No services configured.</p></div>';
}
}
function renderSidebarSupport(supportServices) {
$sidebarSupport.innerHTML = "";
// ── Update System button (above Tech Help)
var sidebarUpdateBtn = document.createElement("button");
sidebarUpdateBtn.className = "sidebar-support-btn";
sidebarUpdateBtn.id = "sidebar-btn-update";
sidebarUpdateBtn.innerHTML =
'<img class="sidebar-support-icon" src="/static/icons/update.svg" alt="Update" style="width:1.5rem;height:1.5rem;">' +
'<span class="sidebar-support-text">' +
'<span class="sidebar-support-title">Update System</span>' +
'<span class="sidebar-support-hint" id="sidebar-update-hint">Check for updates</span>' +
'</span>';
sidebarUpdateBtn.addEventListener("click", function() { openUpdateModal(); });
$sidebarSupport.appendChild(sidebarUpdateBtn);
for (var i = 0; i < supportServices.length; i++) {
var svc = supportServices[i];
var btn = document.createElement("button");
btn.className = "sidebar-support-btn";
btn.innerHTML =
'<span class="sidebar-support-icon">🛟</span>' +
'<span class="sidebar-support-text">' +
'<span class="sidebar-support-title">' + escHtml(svc.name || "Tech Support") + '</span>' +
'<span class="sidebar-support-hint">Click for help</span>' +
'</span>';
btn.addEventListener("click", function() { openSupportModal(); });
$sidebarSupport.appendChild(btn);
}
// ── Manual Backup button
var backupBtn = document.createElement("button");
backupBtn.className = "sidebar-support-btn";
backupBtn.innerHTML =
'<span class="sidebar-support-icon">💾</span>' +
'<span class="sidebar-support-text">' +
'<span class="sidebar-support-title">Manual Backup</span>' +
'<span class="sidebar-support-hint">Back up to external drive</span>' +
'</span>';
backupBtn.addEventListener("click", function() { openBackupModal(); });
$sidebarSupport.appendChild(backupBtn);
// ── Security button
var securityBtn = document.createElement("button");
securityBtn.className = "sidebar-support-btn";
securityBtn.innerHTML =
'<span class="sidebar-support-icon">\uD83D\uDEE1</span>' +
'<span class="sidebar-support-text">' +
'<span class="sidebar-support-title">Security</span>' +
'<span class="sidebar-support-hint">Reset &amp; verify system</span>' +
'</span>';
securityBtn.addEventListener("click", function() { openSecurityModal(); });
$sidebarSupport.appendChild(securityBtn);
// ── Upgrade button (Node role only)
if (_currentRole === "node") {
var upgradeBtn = document.createElement("button");
upgradeBtn.className = "sidebar-support-btn";
upgradeBtn.innerHTML =
'<span class="sidebar-support-icon">🚀</span>' +
'<span class="sidebar-support-text">' +
'<span class="sidebar-support-title">Upgrade to Full Server</span>' +
'<span class="sidebar-support-hint">Unlock all services</span>' +
'</span>';
upgradeBtn.addEventListener("click", function() { openUpgradeModal(); });
$sidebarSupport.appendChild(upgradeBtn);
}
var hr = document.createElement("hr");
hr.className = "sidebar-divider";
$sidebarSupport.appendChild(hr);
}
function buildTile(svc) {
var isSupport = svc.type === "support";
var sc = statusClass(svc.health || svc.status);
var st = statusText(svc.health || svc.status, svc.enabled);
var dis = !svc.enabled;
var tile = document.createElement("div");
tile.className = "service-tile" + (dis ? " disabled" : "") + (isSupport ? " support-tile" : "");
tile.dataset.unit = svc.unit;
tile.dataset.tileId = tileId(svc);
if (isSupport) {
tile.innerHTML = '<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><div class="tile-icon-fallback" style="display:none">?</div><div class="tile-name">' + escHtml(svc.name) + '</div><div class="tile-status"><span class="support-status-label">Click for help</span></div>';
tile.style.cursor = "pointer";
tile.addEventListener("click", function() { openSupportModal(); });
return tile;
}
if (svc.sync_ibd && svc.enabled) {
var pct = Math.round((svc.sync_progress || 0) * 100);
var id = tileId(svc);
var eta = _calcBtcEta(id, svc.sync_progress || 0);
var ver = svc.version || svc.bitcoin_version || '';
var versionLabel = ver ? '<div class="tile-version">' + escHtml(ver) + '</div>' : '';
tile.innerHTML =
'<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'">' +
'<div class="tile-icon-fallback" style="display:none">?</div>' +
'<div class="tile-name">' + escHtml(svc.name) + '</div>' +
versionLabel +
'<div class="tile-sync-container">' +
'<div class="tile-sync-label">\u23F3 Syncing Timechain</div>' +
'<div class="tile-sync-bar-row">' +
'<div class="tile-sync-bar-track"><div class="tile-sync-bar-fill" style="width:' + pct + '%"></div></div>' +
'<span class="tile-sync-percent">' + pct + '%</span>' +
'</div>' +
'<div class="tile-sync-eta">' + escHtml(eta) + '</div>' +
'</div>';
tile.style.cursor = "pointer";
tile.addEventListener("click", function() {
openServiceDetailModal(svc.unit, svc.name, svc.icon);
});
return tile;
}
var ver = svc.version || svc.bitcoin_version || '';
var versionLabel = ver ? '<div class="tile-version">' + escHtml(ver) + '</div>' : '';
tile.innerHTML = '<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><div class="tile-icon-fallback" style="display:none">?</div><div class="tile-name">' + escHtml(svc.name) + '</div>' + versionLabel + '<div class="tile-status"><span class="status-dot ' + sc + '"></span><span class="status-text">' + st + '</span></div>';
tile.style.cursor = "pointer";
tile.addEventListener("click", function() {
openServiceDetailModal(svc.unit, svc.name, svc.icon);
});
return tile;
}
// ── Render: live update ───────────────────────────────────────────
// Calculate ETA text for Bitcoin IBD and track progress history.
function _calcBtcEta(id, progress) {
var now = Date.now();
var prev = _btcSyncPrev[id];
// Only update the cache when progress has actually advanced
if (!prev || prev.progress < progress) {
_btcSyncPrev[id] = { progress: progress, timestamp: now };
}
if (!prev || prev.progress >= progress) return "Estimating\u2026";
var elapsed = (now - prev.timestamp) / 1000; // seconds
if (elapsed <= 0) return "Estimating\u2026";
var rate = (progress - prev.progress) / elapsed; // progress per second
if (rate <= 0) return "Estimating\u2026";
var remaining = (1.0 - progress) / rate;
return "\u007E" + formatDuration(remaining) + " remaining";
}
function updateTiles(services) {
_servicesCache = services;
for (var i = 0; i < services.length; i++) {
var svc = services[i];
if (svc.type === "support") continue;
var id = CSS.escape(tileId(svc));
var tile = $tilesArea.querySelector('.service-tile[data-tile-id="' + id + '"]');
if (!tile) continue;
if (svc.sync_ibd && svc.enabled) {
// If tile was previously normal, rebuild it with the sync layout
if (!tile.querySelector(".tile-sync-container")) {
var newTile = buildTile(svc);
tile.parentNode.replaceChild(newTile, tile);
continue;
}
// Update progress bar values in-place
var pct = Math.round((svc.sync_progress || 0) * 100);
var etaText = _calcBtcEta(tileId(svc), svc.sync_progress || 0);
var fill = tile.querySelector(".tile-sync-bar-fill");
var pctEl = tile.querySelector(".tile-sync-percent");
var etaEl = tile.querySelector(".tile-sync-eta");
if (fill) fill.style.width = pct + "%";
if (pctEl) pctEl.textContent = pct + "%";
if (etaEl) etaEl.textContent = etaText;
// Update or insert version label
var syncVer = svc.version || svc.bitcoin_version || '';
if (syncVer) {
var syncVerEl = tile.querySelector(".tile-version");
if (syncVerEl) {
syncVerEl.textContent = syncVer;
} else {
var syncNameEl = tile.querySelector(".tile-name");
if (syncNameEl) {
var newSyncVerEl = document.createElement("div");
newSyncVerEl.className = "tile-version";
newSyncVerEl.textContent = syncVer;
syncNameEl.insertAdjacentElement("afterend", newSyncVerEl);
}
}
}
} else {
// IBD finished or not syncing — if tile had sync layout rebuild it normally
if (tile.querySelector(".tile-sync-container")) {
delete _btcSyncPrev[tileId(svc)];
var normalTile = buildTile(svc);
tile.parentNode.replaceChild(normalTile, tile);
continue;
}
var sc = statusClass(svc.health || svc.status);
var st = statusText(svc.health || svc.status, svc.enabled);
var dot = tile.querySelector(".status-dot");
var text = tile.querySelector(".status-text");
if (dot) dot.className = "status-dot " + sc;
if (text) text.textContent = st;
// Update or insert version label for all service tiles
var tileVer = svc.version || svc.bitcoin_version || '';
if (tileVer) {
var verEl = tile.querySelector(".tile-version");
if (verEl) {
verEl.textContent = tileVer;
} else {
var nameEl = tile.querySelector(".tile-name");
if (nameEl) {
var newVerEl = document.createElement("div");
newVerEl.className = "tile-version";
newVerEl.textContent = tileVer;
nameEl.insertAdjacentElement("afterend", newVerEl);
}
}
}
}
}
}
// ── Service polling ───────────────────────────────────────────────
var _firstLoad = true;
async function refreshServices() {
try {
var services = await apiFetch("/api/services");
if (_firstLoad) { buildTiles(services, _categoryLabels); _firstLoad = false; }
else { updateTiles(services); }
} catch (err) { console.warn("Failed to fetch services:", err); }
}
// ── Network IPs ───────────────────────────────────────────────────
async function loadNetwork() {
try {
var data = await apiFetch("/api/network");
if ($internalIp) $internalIp.textContent = data.internal_ip || "—";
if ($externalIp) $externalIp.textContent = data.external_ip || "—";
_cachedExternalIp = data.external_ip || "unavailable";
} catch (_) {
if ($internalIp) $internalIp.textContent = "—";
if ($externalIp) $externalIp.textContent = "—";
}
}
// ── Update check ──────────────────────────────────────────────────
async function checkUpdates() {
try {
var data = await apiFetch("/api/updates/check");
var hasUpdates = !!data.available;
var sidebarUpdateBtn = document.getElementById("sidebar-btn-update");
var sidebarUpdateHint = document.getElementById("sidebar-update-hint");
if (sidebarUpdateBtn) {
if (hasUpdates) {
sidebarUpdateBtn.style.borderColor = "#2ec27e";
sidebarUpdateBtn.style.backgroundColor = "rgba(46, 194, 126, 0.08)";
if (sidebarUpdateHint) sidebarUpdateHint.textContent = "Updates available!";
} else {
sidebarUpdateBtn.style.borderColor = "";
sidebarUpdateBtn.style.backgroundColor = "";
if (sidebarUpdateHint) sidebarUpdateHint.textContent = "System is up to date";
}
}
} catch (_) {}
}
@@ -1,222 +0,0 @@
"use strict";
// ── Update modal ──────────────────────────────────────────────────
function openUpdateModal() {
if (!$modal) return;
apiFetch("/api/updates/check")
.then(function(data) {
if (!data.available) {
stopUpdatePoll();
_updateLog = "";
_updateLogOffset = 0;
_updateFinished = true;
if ($modalLog) $modalLog.textContent = "";
if ($modalStatus) $modalStatus.textContent = "✓ System is already up to date";
if ($modalSpinner) $modalSpinner.classList.remove("spinning");
if ($btnReboot) $btnReboot.style.display = "none";
if ($btnSave) $btnSave.style.display = "none";
if ($btnCloseModal) $btnCloseModal.disabled = false;
$modal.classList.add("open");
return;
}
_doOpenUpdateModal();
})
.catch(function() {
_doOpenUpdateModal();
});
}
function _doOpenUpdateModal() {
if (!$modal) return;
_updateLog = "";
_updateLogOffset = 0;
_serverWasDown = false;
_updateFinished = false;
if ($modalLog) $modalLog.textContent = "";
if ($modalStatus) $modalStatus.textContent = "Starting update…";
if ($modalSpinner) $modalSpinner.classList.add("spinning");
if ($btnReboot) $btnReboot.style.display = "none";
if ($btnSave) $btnSave.style.display = "none";
if ($btnCloseModal) $btnCloseModal.disabled = true;
$modal.classList.add("open");
startUpdate();
}
function closeUpdateModal() {
if (!$modal) return;
$modal.classList.remove("open");
stopUpdatePoll();
}
function appendLog(text) {
if (!text) return;
_updateLog += text;
if ($modalLog) { $modalLog.textContent += text; $modalLog.scrollTop = $modalLog.scrollHeight; }
}
function startUpdate() {
fetch("/api/updates/run", { method: "POST" })
.then(function(response) {
if (!response.ok) return response.text().then(function(t) { throw new Error(t); });
return response.json();
})
.then(function(data) {
if (data.status === "no_updates") {
if ($modalStatus) $modalStatus.textContent = "✓ System is already up to date";
if ($modalSpinner) $modalSpinner.classList.remove("spinning");
if ($btnReboot) $btnReboot.style.display = "none";
if ($btnSave) $btnSave.style.display = "none";
if ($btnCloseModal) $btnCloseModal.disabled = false;
_updateFinished = true;
return;
}
if (data.status === "already_running") appendLog("[Update already in progress, attaching…]\n\n");
if ($modalStatus) $modalStatus.textContent = "Updating…";
startUpdatePoll();
})
.catch(function(err) {
appendLog("[Error: failed to start update — " + err + "]\n");
onUpdateDone(false);
});
}
function startUpdatePoll() {
pollUpdateStatus();
_updatePollTimer = setInterval(pollUpdateStatus, UPDATE_POLL_INTERVAL);
}
function stopUpdatePoll() {
if (_updatePollTimer) { clearInterval(_updatePollTimer); _updatePollTimer = null; }
}
async function pollUpdateStatus() {
if (_updateFinished) return;
try {
var data = await apiFetch("/api/updates/status?offset=" + _updateLogOffset);
if (_serverWasDown) {
_serverWasDown = false;
if (!data.running) {
// The update finished while the server was restarting. Reset to
// offset 0 and re-fetch so the complete log is shown from the top.
_updateLog = "";
_updateLogOffset = 0;
if ($modalLog) $modalLog.textContent = "";
try {
var fullData = await apiFetch("/api/updates/status?offset=0");
if (fullData.log) appendLog(fullData.log);
_updateLogOffset = fullData.offset;
} catch (e) {
// If the re-fetch fails, fall through with whatever we have.
if (data.log) appendLog(data.log);
_updateLogOffset = data.offset;
}
if (data.result === "reboot_required") {
appendLog("[Server restarted — update completed, reboot required.]\n");
} else if (data.result === "success") {
appendLog("[Server restarted — update completed successfully.]\n");
} else {
appendLog("[Server restarted — update encountered an error.]\n");
}
_updateFinished = true;
stopUpdatePoll();
if (data.result === "reboot_required") {
onUpdateDone("reboot_required");
} else {
onUpdateDone(data.result === "success");
}
return;
}
appendLog("[Server reconnected]\n");
if ($modalStatus) $modalStatus.textContent = "Updating…";
}
if (data.log) appendLog(data.log);
_updateLogOffset = data.offset;
if (data.running) return;
_updateFinished = true;
stopUpdatePoll();
if (data.result === "reboot_required") {
onUpdateDone("reboot_required");
} else if (data.result === "success") {
onUpdateDone(true);
} else {
onUpdateDone(false);
}
} catch (err) {
if (!_serverWasDown) { _serverWasDown = true; appendLog("\n[Server restarting — waiting for it to come back…]\n"); if ($modalStatus) $modalStatus.textContent = "Server restarting…"; }
}
}
function onUpdateDone(result) {
if ($modalSpinner) $modalSpinner.classList.remove("spinning");
if ($btnCloseModal) $btnCloseModal.disabled = false;
if (result === true) {
if ($modalStatus) $modalStatus.textContent = "✓ Update complete";
if ($btnReboot) $btnReboot.style.display = "inline-flex";
} else if (result === "reboot_required") {
if ($modalStatus) $modalStatus.textContent = "✓ Update complete — reboot required";
if ($btnReboot) $btnReboot.style.display = "inline-flex";
} else {
if ($modalStatus) $modalStatus.textContent = "✗ Update failed";
if ($btnSave) $btnSave.style.display = "inline-flex";
if ($btnReboot) $btnReboot.style.display = "inline-flex";
}
}
function saveErrorReport() {
var blob = new Blob([_updateLog], { type: "text/plain" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = "sovran-update-error-" + new Date().toISOString().split(".")[0].replace(/:/g, "-") + ".txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// ── Reboot ────────────────────────────────────────────────────────
var _rebootStartTime = 0;
var _serverWentDown = false;
function doReboot() {
if ($modal) $modal.classList.remove("open");
if ($rebuildModal) $rebuildModal.classList.remove("open");
stopUpdatePoll();
stopRebuildPoll();
if ($rebootOverlay) $rebootOverlay.classList.add("visible");
_rebootStartTime = Date.now();
_serverWentDown = false;
var rebootCtrl = new AbortController();
setTimeout(function() { rebootCtrl.abort(); }, REBOOT_REQUEST_TIMEOUT);
fetch("/api/reboot", { method: "POST", signal: rebootCtrl.signal }).catch(function() {});
// Wait before the first check — NixOS shutdown after an update can take 20-40s
setTimeout(waitForServerReboot, REBOOT_INITIAL_DELAY);
}
function waitForServerReboot() {
var controller = new AbortController();
var timeoutId = setTimeout(function() { controller.abort(); }, REBOOT_FETCH_TIMEOUT);
fetch("/api/ping", { cache: "no-store", signal: controller.signal, headers: { "Connection": "close" } })
.then(function(res) {
clearTimeout(timeoutId);
if (_serverWentDown) {
// Server is responding after having been down — reboot is complete.
// Any response (even 401/500) means the server process is back.
window.location.reload();
} else if ((Date.now() - _rebootStartTime) < 90000) {
// Server still responding but hasn't gone down yet — keep waiting
setTimeout(waitForServerReboot, REBOOT_CHECK_INTERVAL);
} else {
// Been over 90 seconds and server is responding — just reload
window.location.reload();
}
})
.catch(function() {
clearTimeout(timeoutId);
_serverWentDown = true;
setTimeout(waitForServerReboot, REBOOT_CHECK_INTERVAL);
});
}
@@ -1,121 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 218.44057 109.75845"
xml:space="preserve"
sodipodi:docname="sovran_systems_2.svg"
width="218.44057"
height="109.75845"
inkscape:export-filename="sovran_systems_2.svg"
inkscape:export-xdpi="169.6163"
inkscape:export-ydpi="169.6163"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs47" /><sodipodi:namedview
id="namedview45"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
showgrid="false" />
<style
type="text/css"
id="style2">
.st0{fill:#1C9954;}
.st1{fill:#077233;}
</style>
<g
id="g30"
transform="matrix(0.32162395,0,0,0.33123626,-75.234275,-114.64087)"
style="fill:#dedede;fill-opacity:1">
<path
d="m 354.93,540.02 h -18.69 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.34,-0.33 -0.5,-0.88 -0.5,-1.63 v -6.92 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 15.91 c 0.51,0 0.9,-0.17 1.15,-0.5 0.26,-0.33 0.38,-0.74 0.38,-1.21 0,-0.67 -0.13,-1.16 -0.38,-1.48 -0.26,-0.31 -0.64,-0.49 -1.15,-0.53 l -8.87,-1.24 c -2.76,-0.39 -4.98,-1.3 -6.65,-2.72 -1.68,-1.42 -2.51,-3.78 -2.51,-7.1 v -6.21 c 0,-3.35 1.08,-5.92 3.25,-7.72 2.17,-1.79 5.16,-2.69 8.99,-2.69 h 16.56 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 7.04 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -13.78 c -0.51,0 -0.91,0.17 -1.18,0.5 -0.28,0.34 -0.41,0.76 -0.41,1.27 0,0.51 0.14,0.95 0.41,1.3 0.28,0.35 0.67,0.55 1.18,0.59 l 8.81,1.18 c 2.76,0.39 4.99,1.3 6.68,2.72 1.69,1.42 2.54,3.78 2.54,7.1 v 6.21 c 0,3.35 -1.09,5.92 -3.28,7.72 -2.19,1.8 -5.18,2.69 -8.96,2.69 z"
id="path4"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 412.05,528.85 c 0,1.81 -0.27,3.46 -0.8,4.94 -0.53,1.48 -1.48,2.74 -2.84,3.78 -1.36,1.04 -3.23,1.86 -5.62,2.45 -2.39,0.59 -5.41,0.89 -9.08,0.89 -3.67,0 -6.7,-0.29 -9.11,-0.89 -2.4,-0.59 -4.29,-1.41 -5.65,-2.45 -1.36,-1.04 -2.31,-2.31 -2.84,-3.78 -0.53,-1.48 -0.8,-3.12 -0.8,-4.94 v -20.17 c 0,-1.81 0.27,-3.46 0.8,-4.94 0.53,-1.48 1.48,-2.75 2.84,-3.81 1.36,-1.06 3.24,-1.89 5.65,-2.48 2.4,-0.59 5.44,-0.89 9.11,-0.89 3.67,0 6.69,0.3 9.08,0.89 2.39,0.59 4.26,1.42 5.62,2.48 1.36,1.06 2.31,2.34 2.84,3.81 0.53,1.48 0.8,3.13 0.8,4.94 z m -23.3,-2.13 c 0,0.79 0.3,1.45 0.89,1.98 0.59,0.53 1.95,0.8 4.08,0.8 2.13,0 3.49,-0.27 4.08,-0.8 0.59,-0.53 0.89,-1.19 0.89,-1.98 v -15.91 c 0,-0.75 -0.3,-1.39 -0.89,-1.92 -0.59,-0.53 -1.95,-0.8 -4.08,-0.8 -2.13,0 -3.49,0.27 -4.08,0.8 -0.59,0.53 -0.89,1.17 -0.89,1.92 z"
id="path6"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 447.12,540.02 h -15.38 c -0.75,0 -1.37,-0.14 -1.86,-0.41 -0.49,-0.28 -0.88,-0.79 -1.15,-1.54 l -5.8,-14.49 c -0.35,-0.87 -0.65,-1.63 -0.89,-2.28 -0.24,-0.65 -0.43,-1.27 -0.59,-1.86 -0.16,-0.59 -0.27,-1.21 -0.33,-1.86 -0.06,-0.65 -0.09,-1.45 -0.09,-2.4 v -15.61 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 9.4 c 0.75,0 1.32,0.17 1.71,0.5 0.39,0.34 0.59,0.88 0.59,1.63 v 16.32 c 0,0.39 0.04,0.79 0.12,1.18 0.08,0.39 0.2,0.81 0.35,1.24 l 2.78,8.28 c 0.12,0.39 0.26,0.66 0.41,0.8 0.16,0.14 0.39,0.21 0.71,0.21 h 0.65 c 0.31,0 0.55,-0.07 0.71,-0.21 0.16,-0.14 0.3,-0.4 0.41,-0.8 l 2.78,-8.34 c 0.16,-0.43 0.28,-0.85 0.35,-1.24 0.08,-0.39 0.12,-0.79 0.12,-1.18 v -16.26 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 9.28 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 15.61 c 0,0.95 -0.03,1.74 -0.09,2.4 -0.06,0.65 -0.17,1.27 -0.33,1.86 -0.16,0.59 -0.35,1.21 -0.59,1.86 -0.24,0.65 -0.53,1.41 -0.89,2.28 l -5.8,14.49 c -0.28,0.75 -0.66,1.26 -1.15,1.54 -0.45,0.28 -1.07,0.41 -1.82,0.41 z"
id="path8"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 478.69,540.02 h -9.11 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.34,-0.33 -0.5,-0.88 -0.5,-1.63 v -38.32 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 19.69 c 4.41,0 7.46,0.92 9.14,2.75 1.68,1.83 2.51,4.21 2.51,7.13 v 2.72 c 0,1.66 -0.25,3.07 -0.74,4.23 -0.49,1.16 -1.35,2 -2.57,2.51 2.13,0.24 3.85,1.1 5.17,2.6 1.32,1.5 1.98,3.49 1.98,5.97 v 12.54 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -9.17 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.34,-0.33 -0.5,-0.88 -0.5,-1.63 v -9.05 c 0,-0.87 -0.17,-1.51 -0.5,-1.92 -0.34,-0.41 -0.92,-0.62 -1.74,-0.62 h -8.28 v 11.59 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.32,0.34 -0.87,0.5 -1.62,0.5 z m 2.13,-31.93 v 7.57 h 4.44 c 1.02,0 1.71,-0.27 2.07,-0.8 0.36,-0.53 0.53,-1.19 0.53,-1.98 v -2.01 c 0,-0.79 -0.18,-1.45 -0.53,-1.98 -0.35,-0.53 -1.04,-0.8 -2.07,-0.8 z"
id="path10"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 525.83,537.89 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 H 515 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.34,-0.33 -0.5,-0.88 -0.5,-1.63 v -15.61 c 0,-1.18 0.19,-2.54 0.56,-4.08 0.38,-1.54 0.96,-3.33 1.75,-5.38 l 5.15,-13.42 c 0.24,-0.67 0.6,-1.16 1.09,-1.48 0.49,-0.31 1.13,-0.47 1.92,-0.47 h 15.91 c 0.75,0 1.37,0.16 1.86,0.47 0.49,0.32 0.86,0.81 1.09,1.48 l 5.14,13.42 c 0.79,2.05 1.37,3.84 1.75,5.38 0.37,1.54 0.56,2.9 0.56,4.08 v 15.61 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -8.93 c -0.79,0 -1.37,-0.17 -1.74,-0.5 -0.38,-0.33 -0.56,-0.88 -0.56,-1.63 v -8.28 h -10.47 v 8.28 z m 3.37,-28.03 -2.78,8.99 h 9.29 l -2.78,-8.99 c -0.16,-0.35 -0.33,-0.61 -0.5,-0.77 -0.18,-0.16 -0.38,-0.24 -0.62,-0.24 h -1.48 c -0.24,0 -0.44,0.08 -0.62,0.24 -0.19,0.16 -0.36,0.42 -0.51,0.77 z"
id="path12"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 570.29,540.02 h -8.87 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.33,-0.33 -0.5,-0.88 -0.5,-1.63 v -38.32 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 6.15 c 0.75,0 1.39,0.12 1.92,0.35 0.53,0.24 1.05,0.65 1.57,1.24 l 11.47,13.13 v -12.6 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 8.87 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 38.32 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -8.87 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.34,-0.33 -0.5,-0.88 -0.5,-1.63 v -7.27 l -10.11,-12.24 v 19.51 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.35 -0.88,0.51 -1.63,0.51 z"
id="path14"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 641.61,540.02 h -18.69 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.33,-0.33 -0.5,-0.88 -0.5,-1.63 v -6.92 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.34 0.88,-0.5 1.63,-0.5 h 15.91 c 0.51,0 0.9,-0.17 1.15,-0.5 0.26,-0.33 0.38,-0.74 0.38,-1.21 0,-0.67 -0.13,-1.16 -0.38,-1.48 -0.26,-0.31 -0.64,-0.49 -1.15,-0.53 l -8.87,-1.24 c -2.76,-0.39 -4.98,-1.3 -6.65,-2.72 -1.68,-1.42 -2.51,-3.78 -2.51,-7.1 v -6.21 c 0,-3.35 1.08,-5.92 3.25,-7.72 2.17,-1.79 5.16,-2.69 8.99,-2.69 h 16.56 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 7.04 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -13.78 c -0.51,0 -0.91,0.17 -1.18,0.5 -0.28,0.34 -0.41,0.76 -0.41,1.27 0,0.51 0.14,0.95 0.41,1.3 0.28,0.35 0.67,0.55 1.18,0.59 l 8.81,1.18 c 2.76,0.39 4.99,1.3 6.68,2.72 1.7,1.42 2.54,3.78 2.54,7.1 v 6.21 c 0,3.35 -1.09,5.92 -3.28,7.72 -2.19,1.8 -5.17,2.69 -8.96,2.69 z"
id="path16"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 683.66,540.02 h -9.58 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.34,-0.33 -0.5,-0.88 -0.5,-1.63 v -7.57 L 662.9,518.2 c -0.91,-1.22 -1.51,-2.29 -1.8,-3.19 -0.3,-0.91 -0.44,-2.27 -0.44,-4.08 v -11.35 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 9.11 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 9.7 c 0,0.39 0.02,0.81 0.06,1.24 0.04,0.43 0.2,0.85 0.47,1.24 l 2.72,4.26 c 0.2,0.35 0.4,0.61 0.62,0.77 0.22,0.16 0.48,0.24 0.8,0.24 h 0.59 c 0.31,0 0.58,-0.08 0.8,-0.24 0.22,-0.16 0.42,-0.41 0.62,-0.77 l 2.72,-4.26 c 0.28,-0.39 0.43,-0.81 0.47,-1.24 0.04,-0.43 0.06,-0.85 0.06,-1.24 v -9.7 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 8.81 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 11.35 c 0,1.81 -0.16,3.17 -0.47,4.08 -0.32,0.91 -0.91,1.97 -1.77,3.19 l -8.99,12.18 v 7.51 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.35,0.34 -0.9,0.5 -1.64,0.5 z"
id="path18"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 725.88,540.02 h -18.69 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.33,-0.33 -0.5,-0.88 -0.5,-1.63 v -6.92 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.34 0.88,-0.5 1.63,-0.5 h 15.91 c 0.51,0 0.9,-0.17 1.15,-0.5 0.26,-0.33 0.38,-0.74 0.38,-1.21 0,-0.67 -0.13,-1.16 -0.38,-1.48 -0.26,-0.31 -0.64,-0.49 -1.15,-0.53 l -8.87,-1.24 c -2.76,-0.39 -4.98,-1.3 -6.65,-2.72 -1.68,-1.42 -2.51,-3.78 -2.51,-7.1 v -6.21 c 0,-3.35 1.08,-5.92 3.25,-7.72 2.17,-1.79 5.16,-2.69 8.99,-2.69 h 16.56 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 7.04 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -13.78 c -0.51,0 -0.91,0.17 -1.18,0.5 -0.28,0.34 -0.41,0.76 -0.41,1.27 0,0.51 0.14,0.95 0.41,1.3 0.28,0.35 0.67,0.55 1.18,0.59 l 8.81,1.18 c 2.76,0.39 4.99,1.3 6.68,2.72 1.7,1.42 2.54,3.78 2.54,7.1 v 6.21 c 0,3.35 -1.09,5.92 -3.28,7.72 -2.19,1.8 -5.18,2.69 -8.96,2.69 z"
id="path20"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 766.44,540.02 h -9.58 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.34,-0.33 -0.5,-0.88 -0.5,-1.63 v -29.04 h -8.69 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.33,-0.33 -0.5,-0.88 -0.5,-1.63 v -7.16 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 31.22 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 7.16 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.33,0.34 -0.88,0.5 -1.63,0.5 h -8.69 v 29.04 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.33,0.34 -0.88,0.5 -1.63,0.5 z"
id="path22"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 817.06,540.02 h -27.44 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.33,-0.33 -0.5,-0.88 -0.5,-1.63 v -38.32 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 27.44 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 6.92 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -16.32 v 4.55 h 11.53 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.33 0.5,0.88 0.5,1.63 v 6.33 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.33 -0.88,0.5 -1.63,0.5 h -11.53 v 5.08 h 16.32 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.33 0.5,0.88 0.5,1.63 v 6.92 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 z"
id="path24"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 839.48,540.02 h -8.81 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.33,-0.33 -0.5,-0.88 -0.5,-1.63 v -38.32 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.33 0.88,-0.5 1.63,-0.5 h 9.52 c 0.63,0 1.15,0.14 1.57,0.41 0.41,0.28 0.8,0.73 1.15,1.36 l 5.32,9.64 c 0.2,0.35 0.36,0.61 0.5,0.77 0.14,0.16 0.33,0.24 0.56,0.24 h 0.53 c 0.24,0 0.42,-0.08 0.56,-0.24 0.14,-0.16 0.3,-0.41 0.5,-0.77 l 5.26,-9.64 c 0.36,-0.63 0.74,-1.08 1.15,-1.36 0.41,-0.28 0.94,-0.41 1.57,-0.41 h 9.58 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 38.32 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -9.11 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.34,-0.33 -0.5,-0.88 -0.5,-1.63 v -20.82 l -3.49,6.45 c -0.35,0.67 -0.78,1.15 -1.27,1.45 -0.49,0.3 -1.12,0.44 -1.86,0.44 h -2.37 c -0.75,0 -1.37,-0.15 -1.86,-0.44 -0.49,-0.29 -0.92,-0.78 -1.27,-1.45 l -3.49,-6.45 v 20.82 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.32,0.34 -0.87,0.5 -1.61,0.5 z"
id="path26"
style="fill:#dedede;fill-opacity:1" />
<path
d="m 900.86,540.02 h -18.69 c -0.75,0 -1.29,-0.17 -1.63,-0.5 -0.33,-0.33 -0.5,-0.88 -0.5,-1.63 v -6.92 c 0,-0.75 0.17,-1.29 0.5,-1.63 0.33,-0.34 0.88,-0.5 1.63,-0.5 h 15.91 c 0.51,0 0.9,-0.17 1.15,-0.5 0.26,-0.33 0.38,-0.74 0.38,-1.21 0,-0.67 -0.13,-1.16 -0.38,-1.48 -0.26,-0.31 -0.64,-0.49 -1.15,-0.53 l -8.87,-1.24 c -2.76,-0.39 -4.98,-1.3 -6.65,-2.72 -1.68,-1.42 -2.51,-3.78 -2.51,-7.1 v -6.21 c 0,-3.35 1.08,-5.92 3.25,-7.72 2.17,-1.79 5.16,-2.69 8.99,-2.69 h 16.56 c 0.75,0 1.29,0.17 1.63,0.5 0.33,0.34 0.5,0.88 0.5,1.63 v 7.04 c 0,0.75 -0.17,1.29 -0.5,1.63 -0.34,0.34 -0.88,0.5 -1.63,0.5 h -13.78 c -0.51,0 -0.91,0.17 -1.18,0.5 -0.28,0.34 -0.41,0.76 -0.41,1.27 0,0.51 0.14,0.95 0.41,1.3 0.28,0.35 0.67,0.55 1.18,0.59 l 8.81,1.18 c 2.76,0.39 4.99,1.3 6.68,2.72 1.7,1.42 2.54,3.78 2.54,7.1 v 6.21 c 0,3.35 -1.09,5.92 -3.28,7.72 -2.19,1.8 -5.18,2.69 -8.96,2.69 z"
id="path28"
style="fill:#dedede;fill-opacity:1" />
</g>
<g
id="g34"
transform="matrix(0.32162395,0,0,0.33123626,-75.234275,-114.64087)">
<path
class="st0"
d="m 399.59,375.57 c -75.1,0 -136.2,61.1 -136.2,136.2 0,75.1 61.1,136.2 136.2,136.2 27.74,0 53.37,-8.55 74.84,-22.9 -1.35,-1.17 -2.61,-2.4 -3.8,-3.72 -20.44,13.4 -44.79,21.29 -71.04,21.29 -72.16,0 -130.87,-58.71 -130.87,-130.87 0,-72.16 58.71,-130.87 130.87,-130.87 47.14,0 88.35,25.15 111.36,62.66 h 6.25 C 493.61,402.98 449.81,375.57 399.59,375.57 Z"
id="path32" />
</g>
<g
id="g38"
transform="matrix(0.32162395,0,0,0.33123626,-75.234275,-114.64087)">
<path
class="st1"
d="m 535.7,566.96 c -21.87,53.75 -74.6,91.78 -136.11,91.78 -81.04,0 -146.98,-65.94 -146.98,-146.98 0,-81.04 65.94,-146.98 146.98,-146.98 29.26,0 56.5,8.66 79.43,23.45 l 3.7,-3.7 c -23.92,-15.69 -52.44,-24.91 -83.13,-24.91 -83.89,0 -152.14,68.24 -152.14,152.14 0,83.9 68.24,152.14 152.14,152.14 64.42,0 119.58,-40.26 141.72,-96.94 z"
id="path36" />
</g>
<g
id="g42"
transform="matrix(0.32162395,0,0,0.33123626,-75.234275,-114.64087)"
inkscape:export-filename="./g42.svg"
inkscape:export-xdpi="169.6163"
inkscape:export-ydpi="169.6163">
<path
class="st0"
d="m 399.59,672.5 c -88.63,0 -160.73,-72.11 -160.73,-160.73 0,-88.62 72.11,-160.73 160.73,-160.73 75.66,0 139.05,52.63 156.04,123.16 h 5.06 C 543.61,400.93 478,346.1 399.6,346.1 c -91.36,0 -165.68,74.32 -165.68,165.68 0,91.36 74.32,165.68 165.68,165.68 56.18,0 105.83,-28.17 135.77,-71.07 -1.21,-1.21 -2.42,-2.42 -3.64,-3.63 -29,42.03 -77.32,69.74 -132.14,69.74 z"
id="path40" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

@@ -1,703 +0,0 @@
/* Sovran_SystemsOS Hub — First-Boot Onboarding Wizard
Drives the 5-step post-install setup flow. */
"use strict";
// ── Constants ─────────────────────────────────────────────────────
const TOTAL_STEPS = 5;
// Steps to skip per role (steps 3 and 4 involve domain/port setup)
// Step 2 (timezone/locale) is NEVER skipped — all roles need it.
const ROLE_SKIP_STEPS = {
"desktop": [3, 4],
"node": [3, 4],
};
// ── Role state (loaded at init) ───────────────────────────────────
var _onboardingRole = "server_plus_desktop";
// Domains that may need configuration, with service unit mapping for enabled check
const DOMAIN_DEFS = [
{ name: "matrix", label: "Matrix (Synapse)", unit: "matrix-synapse.service", needsDdns: true },
{ name: "haven", label: "Haven Nostr Relay", unit: "haven-relay.service", needsDdns: true },
{ name: "element-calling", label: "Element Video/Audio Calling", unit: "livekit.service", needsDdns: true },
{ name: "vaultwarden", label: "Vaultwarden (Password Vault)", unit: "vaultwarden.service", needsDdns: true },
{ name: "btcpayserver", label: "BTCPay Server", unit: "btcpayserver.service", needsDdns: true },
{ name: "nextcloud", label: "Nextcloud", unit: "phpfpm-nextcloud.service", needsDdns: true },
{ name: "wordpress", label: "WordPress", unit: "phpfpm-wordpress.service", needsDdns: true },
];
// ── State ─────────────────────────────────────────────────────────
var _currentStep = 1;
var _servicesData = null;
var _domainsData = null;
var _migrationOccurred = false;
// ── Helpers ───────────────────────────────────────────────────────
function escHtml(str) {
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
async function apiFetch(path, options) {
var res = await fetch(path, options || {});
if (!res.ok) {
var detail = res.status + " " + res.statusText;
try {
var body = await res.json();
if (body && body.detail) detail = body.detail;
} catch (e) {}
throw new Error(detail);
}
return res.json();
}
function setStatus(elId, msg, type) {
var el = document.getElementById(elId);
if (!el) return;
el.textContent = msg;
el.className = "onboarding-save-status" + (type ? " onboarding-save-status--" + type : "");
}
function updateStep5Checklist() {
var checklist = document.getElementById("onboarding-checklist");
if (!checklist) return;
var existing = document.getElementById("onboarding-migration-check");
if (_migrationOccurred) {
if (!existing) {
var li = document.createElement("li");
li.id = "onboarding-migration-check";
li.textContent = "✅ Migration password noted";
checklist.appendChild(li);
}
return;
}
if (existing) existing.remove();
}
function showMigrationStep(password) {
for (var i = 1; i <= TOTAL_STEPS; i++) {
var panel = document.getElementById("step-" + i);
if (panel) panel.style.display = "none";
}
var migrationPanel = document.getElementById("step-migration");
if (migrationPanel) migrationPanel.style.display = "";
var pw = document.getElementById("migration-password-value");
if (pw) pw.textContent = password || "";
var progressBar = document.getElementById("onboarding-progress-bar");
if (progressBar) progressBar.style.display = "none";
var nav = document.getElementById("onboarding-steps-nav");
if (nav) nav.style.display = "none";
}
function showStep1FromMigration() {
var migrationPanel = document.getElementById("step-migration");
if (migrationPanel) migrationPanel.style.display = "none";
var progressBar = document.getElementById("onboarding-progress-bar");
if (progressBar) progressBar.style.display = "";
var nav = document.getElementById("onboarding-steps-nav");
if (nav) nav.style.display = "";
showStep(1);
loadStep1();
}
// ── Progress / step navigation ────────────────────────────────────
function updateProgress(step) {
var fill = document.getElementById("onboarding-progress-fill");
if (fill) {
fill.style.width = Math.round(((step - 1) / (TOTAL_STEPS - 1)) * 100) + "%";
}
var dots = document.querySelectorAll(".onboarding-step-dot");
dots.forEach(function(dot) {
var ds = parseInt(dot.dataset.step, 10);
dot.classList.remove("active", "completed");
if (ds < step) dot.classList.add("completed");
if (ds === step) dot.classList.add("active");
});
}
function showStep(step) {
for (var i = 1; i <= TOTAL_STEPS; i++) {
var panel = document.getElementById("step-" + i);
if (panel) panel.style.display = (i === step) ? "" : "none";
}
_currentStep = step;
updateProgress(step);
// Lazy-load step content
if (step === 2) loadStep2();
if (step === 3) loadStep3();
if (step === 4) loadStep4();
// Step 5 (Complete) is static — no lazy-load needed
}
// Return the next step number, skipping over role-excluded steps
function nextStep(current) {
var skip = ROLE_SKIP_STEPS[_onboardingRole] || [];
var next = current + 1;
while (next < TOTAL_STEPS && skip.indexOf(next) !== -1) next++;
return next;
}
// Return the previous step number, skipping over role-excluded steps
function prevStep(current) {
var skip = ROLE_SKIP_STEPS[_onboardingRole] || [];
var prev = current - 1;
while (prev > 1 && skip.indexOf(prev) !== -1) prev--;
return prev;
}
// ── Step 1: Welcome ───────────────────────────────────────────────
async function loadStep1() {
try {
var cfg = await apiFetch("/api/config");
var badge = document.getElementById("onboarding-role-badge");
if (badge && cfg.role_label) badge.textContent = cfg.role_label;
} catch (_) {}
}
// ── Step 2: Timezone & Locale ─────────────────────────────────────
async function loadStep2() {
var body = document.getElementById("step-2-body");
if (!body || body._tzLoaded) return;
body._tzLoaded = true;
body.innerHTML = '<p class="onboarding-loading">Loading timezone data…</p>';
var timezones = [];
var currentTz = null;
var locales = [];
var currentLocale = null;
try {
var results = await Promise.all([
apiFetch("/api/system/timezones"),
apiFetch("/api/system/locales"),
]);
timezones = results[0].timezones || [];
currentTz = results[0].current || null;
locales = results[1].locales || [];
currentLocale = results[1].current || null;
} catch (err) {
body.innerHTML = '<p class="onboarding-error">⚠ Could not load timezone data: ' + escHtml(err.message) + '</p>';
return;
}
// Try to auto-detect timezone from browser
var browserTz = null;
try { browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone; } catch (_) {}
var selectedTz = currentTz || browserTz || "";
var html = '';
// Timezone section
html += '<div class="onboarding-tz-group">';
html += '<label class="onboarding-domain-label" for="tz-search">🕐 Timezone</label>';
html += '<input class="onboarding-tz-search" type="text" id="tz-search" placeholder="Search timezones…" autocomplete="off" value="' + escHtml(selectedTz) + '" />';
html += '<select class="onboarding-tz-select" id="tz-select" size="5">';
timezones.forEach(function(tz) {
var sel = tz === selectedTz ? ' selected' : '';
html += '<option value="' + escHtml(tz) + '"' + sel + '>' + escHtml(tz) + '</option>';
});
html += '</select>';
html += '<p class="onboarding-hint">Current: <span id="tz-current-display">' + escHtml(selectedTz || 'Not set') + '</span></p>';
html += '</div>';
// Locale section
html += '<div class="onboarding-tz-group">';
html += '<label class="onboarding-domain-label" for="locale-select">🌐 Language / Locale</label>';
html += '<select class="onboarding-tz-select onboarding-locale-select" id="locale-select">';
locales.forEach(function(loc) {
var sel = loc === (currentLocale || 'en_US.UTF-8') ? ' selected' : '';
html += '<option value="' + escHtml(loc) + '"' + sel + '>' + escHtml(loc) + '</option>';
});
html += '</select>';
html += '</div>';
body.innerHTML = html;
// Wire timezone search filter
var tzSearch = document.getElementById('tz-search');
var tzSelect = document.getElementById('tz-select');
var tzCurrentDisplay = document.getElementById('tz-current-display');
if (tzSearch && tzSelect) {
// When typing in search box, filter the dropdown options
tzSearch.addEventListener('input', function() {
var q = tzSearch.value.toLowerCase();
var opts = tzSelect.options;
var firstVisible = null;
for (var i = 0; i < opts.length; i++) {
var match = opts[i].value.toLowerCase().indexOf(q) !== -1;
opts[i].style.display = match ? '' : 'none';
if (match && firstVisible === null) firstVisible = i;
}
if (firstVisible !== null) {
tzSelect.selectedIndex = firstVisible;
if (tzCurrentDisplay) tzCurrentDisplay.textContent = tzSelect.options[firstVisible].value;
}
});
// When selecting from dropdown, update search box
tzSelect.addEventListener('change', function() {
if (tzSelect.value) {
tzSearch.value = tzSelect.value;
if (tzCurrentDisplay) tzCurrentDisplay.textContent = tzSelect.value;
}
});
// Scroll selected option into view
if (tzSelect.selectedIndex >= 0) {
tzSelect.options[tzSelect.selectedIndex].scrollIntoView({ block: 'nearest' });
}
}
}
async function saveStep2() {
var tzSelect = document.getElementById('tz-select');
var tzSearch = document.getElementById('tz-search');
var localeSelect = document.getElementById('locale-select');
// Determine selected timezone: prefer dropdown selection, fall back to search text
var tz = (tzSelect && tzSelect.value) ? tzSelect.value : (tzSearch ? tzSearch.value.trim() : '');
var locale = localeSelect ? localeSelect.value : '';
if (!tz) {
setStatus('step-2-status', '⚠ Please select a timezone.', 'error');
return false;
}
setStatus('step-2-status', 'Saving…', 'info');
var errors = [];
try {
await apiFetch('/api/system/timezone', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ timezone: tz }),
});
} catch (err) {
errors.push('Timezone: ' + err.message);
}
if (locale) {
try {
await apiFetch('/api/system/locale', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ locale: locale }),
});
} catch (err) {
errors.push('Locale: ' + err.message);
}
}
if (errors.length > 0) {
setStatus('step-2-status', '⚠ ' + errors.join('; '), 'error');
return false;
}
setStatus('step-2-status', '✓ Timezone & locale saved', 'ok');
return true;
}
// ── Step 3: Domain Configuration ─────────────────────────────────
async function loadStep3() {
var body = document.getElementById("step-3-body");
if (!body) return;
try {
// Fetch services, domains, and network info in parallel
var results = await Promise.all([
apiFetch("/api/services"),
apiFetch("/api/domains/status"),
apiFetch("/api/network"),
]);
_servicesData = results[0];
_domainsData = results[1];
var networkData = results[2];
} catch (err) {
body.innerHTML = '<p class="onboarding-error">⚠ Could not load service data: ' + escHtml(err.message) + '</p>';
return;
}
var externalIp = (networkData && networkData.external_ip) || "Unknown (could not retrieve)";
// Build set of enabled service units
var enabledUnits = new Set();
(_servicesData || []).forEach(function(svc) {
if (svc.enabled) enabledUnits.add(svc.unit);
});
// Filter domain defs to only those whose service is enabled
var relevantDomains = DOMAIN_DEFS.filter(function(d) {
return enabledUnits.has(d.unit);
});
var html = "";
if (relevantDomains.length === 0) {
html += '<p class="onboarding-body-text">No domain-based services are enabled for your role. You can skip this step.</p>';
} else {
html += '<div class="onboarding-port-warn" style="margin-bottom:16px;">'
+ '<strong>Before you continue:</strong>'
+ '<ol style="margin:8px 0 0 16px; padding:0; line-height:1.7;">'
+ '<li>Create an account at <a href="https://njal.la" target="_blank" style="color:var(--accent-color);">https://njal.la</a></li>'
+ '<li>Purchase a new domain on Njal.la, or create a subdomain from a domain you already own. Tip: Subdomains are free to create — you only need to purchase one domain, and you can add as many subdomains as you need at no extra cost.</li>'
+ '<li>In the Njal.la web interface, create a <strong>Dynamic</strong> record pointing to this machine\'s external IP address:<br>'
+ '<span style="display:inline-block;margin-top:4px;padding:4px 12px;background:var(--card-color);border:1px solid var(--border-color);border-radius:6px;font-family:monospace;font-size:1.1em;font-weight:700;letter-spacing:0.03em;">' + escHtml(externalIp) + '</span></li>'
+ '<li>Njal.la will give you a curl command like:<br>'
+ '<code style="font-size:0.8em;">curl "https://njal.la/update/?h=sub.domain.com&amp;k=abc123&amp;auto"</code></li>'
+ '<li>Enter the subdomain and paste that curl command below for each service</li>'
+ '</ol>'
+ '</div>';
html += '<p class="onboarding-hint">Enter each fully-qualified subdomain (e.g. <code>matrix.yourdomain.com</code>) and its Njal.la DDNS curl command.</p>';
relevantDomains.forEach(function(d) {
var currentVal = (_domainsData && _domainsData[d.name]) || "";
html += '<div class="onboarding-domain-group">';
html += '<label class="onboarding-domain-label">' + escHtml(d.label) + '</label>';
html += '<input class="onboarding-domain-input domain-field-input" type="text" id="domain-input-' + escHtml(d.name) + '" data-domain="' + escHtml(d.name) + '" placeholder="e.g. ' + escHtml(d.name) + '.yourdomain.com" value="' + escHtml(currentVal) + '" />';
html += '<label class="onboarding-domain-label onboarding-domain-label--sub">Njal.la DDNS Curl Command</label>';
html += '<input class="onboarding-domain-input domain-field-input" type="text" id="ddns-input-' + escHtml(d.name) + '" data-ddns="' + escHtml(d.name) + '" placeholder="curl &quot;https://njal.la/update/?h=' + escHtml(d.name) + '.yourdomain.com&amp;k=abc123&amp;auto&quot;" />';
html += '<p class="onboarding-hint" style="margin-top:4px;"> Paste the curl URL from your Njal.la dashboard\'s Dynamic record</p>';
html += '<button type="button" class="btn btn-primary onboarding-domain-save-btn" data-save-domain="' + escHtml(d.name) + '" style="align-self:flex-start;margin-top:8px;font-size:0.82rem;padding:6px 16px;">Save</button>';
html += '<span class="onboarding-domain-save-status" id="domain-save-status-' + escHtml(d.name) + '" style="font-size:0.82rem;min-height:1.2em;"></span>';
html += '</div>';
});
}
// SSL email section
var emailVal = (_domainsData && _domainsData["sslemail"]) || "";
html += '<div class="onboarding-domain-group onboarding-domain-group--email">';
html += '<label class="onboarding-domain-label">📧 SSL Certificate Email</label>';
html += '<p class="onboarding-hint onboarding-hint--inline">Let\'s Encrypt uses this for certificate expiry notifications.</p>';
html += '<input class="onboarding-domain-input domain-field-input" type="email" id="ssl-email-input" placeholder="you@example.com" value="' + escHtml(emailVal) + '" />';
html += '<button type="button" class="btn btn-primary onboarding-domain-save-btn" data-save-email="true" style="align-self:flex-start;margin-top:8px;font-size:0.82rem;padding:6px 16px;">Save</button>';
html += '<span class="onboarding-domain-save-status" id="domain-save-status-email" style="font-size:0.82rem;min-height:1.2em;"></span>';
html += '</div>';
body.innerHTML = html;
// Wire per-field save buttons for domains
body.querySelectorAll('[data-save-domain]').forEach(function(btn) {
btn.addEventListener('click', async function() {
var domainName = btn.dataset.saveDomain;
var domainInput = document.getElementById('domain-input-' + domainName);
var ddnsInput = document.getElementById('ddns-input-' + domainName);
var statusEl = document.getElementById('domain-save-status-' + domainName);
var domainVal = domainInput ? domainInput.value.trim() : '';
var ddnsVal = ddnsInput ? ddnsInput.value.trim() : '';
if (!domainVal) {
if (statusEl) { statusEl.textContent = '⚠ Enter a domain first'; statusEl.style.color = 'var(--red)'; }
return;
}
btn.disabled = true;
btn.textContent = 'Saving…';
if (statusEl) { statusEl.textContent = ''; }
try {
await apiFetch('/api/domains/set', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ domain_name: domainName, domain: domainVal, ddns_url: ddnsVal }),
});
if (statusEl) { statusEl.textContent = '✓ Saved'; statusEl.style.color = 'var(--green)'; }
} catch (err) {
if (statusEl) { statusEl.textContent = '⚠ ' + err.message; statusEl.style.color = 'var(--red)'; }
}
btn.disabled = false;
btn.textContent = 'Save';
});
});
// Wire save button for SSL email
body.querySelectorAll('[data-save-email]').forEach(function(btn) {
btn.addEventListener('click', async function() {
var emailInput = document.getElementById('ssl-email-input');
var statusEl = document.getElementById('domain-save-status-email');
var emailVal = emailInput ? emailInput.value.trim() : '';
if (!emailVal) {
if (statusEl) { statusEl.textContent = '⚠ Enter an email first'; statusEl.style.color = 'var(--red)'; }
return;
}
btn.disabled = true;
btn.textContent = 'Saving…';
if (statusEl) { statusEl.textContent = ''; }
try {
await apiFetch('/api/domains/set-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: emailVal }),
});
if (statusEl) { statusEl.textContent = '✓ Saved'; statusEl.style.color = 'var(--green)'; }
} catch (err) {
if (statusEl) { statusEl.textContent = '⚠ ' + err.message; statusEl.style.color = 'var(--red)'; }
}
btn.disabled = false;
btn.textContent = 'Save';
});
});
}
async function saveStep3() {
setStatus("step-3-status", "Saving domains…", "info");
var errors = [];
// Save each domain input
var domainInputs = document.querySelectorAll("[data-domain]");
for (var i = 0; i < domainInputs.length; i++) {
var inp = domainInputs[i];
var domainName = inp.dataset.domain;
var domainVal = inp.value.trim();
if (!domainVal) continue; // skip empty — not required
var ddnsInput = document.getElementById("ddns-input-" + domainName);
var ddnsVal = ddnsInput ? ddnsInput.value.trim() : "";
try {
await apiFetch("/api/domains/set", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ domain_name: domainName, domain: domainVal, ddns_url: ddnsVal }),
});
} catch (err) {
errors.push(domainName + ": " + err.message);
}
}
// Save SSL email
var emailInput = document.getElementById("ssl-email-input");
if (emailInput && emailInput.value.trim()) {
try {
await apiFetch("/api/domains/set-email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: emailInput.value.trim() }),
});
} catch (err) {
errors.push("SSL email: " + err.message);
}
}
if (errors.length > 0) {
setStatus("step-3-status", "⚠ Some errors: " + errors.join("; "), "error");
return false;
}
setStatus("step-3-status", "✓ Saved", "ok");
return true;
}
// ── Step 4: Port Forwarding ───────────────────────────────────────
async function loadStep4() {
var body = document.getElementById("step-4-body");
if (!body) return;
body.innerHTML = '<p class="onboarding-loading">Checking ports…</p>';
var networkData = null;
try {
networkData = await apiFetch("/api/network");
} catch (err) {
body.innerHTML = '<p class="onboarding-error">⚠ Could not load network data: ' + escHtml(err.message) + '</p>';
return;
}
var internalIp = (networkData && networkData.internal_ip) || "unknown";
var ip = escHtml(internalIp);
var html = '<p class="onboarding-port-note" style="margin-bottom:14px;">'
+ '⚠ <strong>Each port only needs to be forwarded once — all services share the same ports.</strong>'
+ '</p>';
html += '<div class="onboarding-port-ip">';
html += ' <span class="onboarding-port-ip-label">Forward ports to this machine\'s internal IP:</span>';
html += ' <span class="port-req-internal-ip">' + ip + '</span>';
html += '</div>';
// Required ports table
html += '<div class="onboarding-port-section" style="margin-bottom:20px;">';
html += '<div class="onboarding-port-section-title" style="font-weight:700;margin-bottom:8px;">Required Ports — open these on your router:</div>';
html += '<table class="onboarding-port-table">';
html += '<thead><tr><th>Port</th><th>Protocol</th><th>Forward&nbsp;to</th><th>Purpose</th></tr></thead>';
html += '<tbody>';
html += '<tr><td class="port-req-port">80</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">HTTP</td></tr>';
html += '<tr><td class="port-req-port">443</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">HTTPS</td></tr>';
html += '<tr><td class="port-req-port">22</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">SSH Remote Access</td></tr>';
html += '<tr><td class="port-req-port">8448</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">Matrix Federation</td></tr>';
html += '</tbody></table>';
html += '</div>';
// Optional ports table
html += '<div class="onboarding-port-section" style="margin-bottom:20px;">';
html += '<div class="onboarding-port-section-title" style="font-weight:700;margin-bottom:4px;">Optional — Only needed if you enable Element Calling:</div>';
html += '<div style="font-size:0.88em;margin-bottom:8px;color:var(--color-text-muted,#888);">These 5 additional port openings are required on top of the 4 required ports above.</div>';
html += '<table class="onboarding-port-table">';
html += '<thead><tr><th>Port</th><th>Protocol</th><th>Forward&nbsp;to</th><th>Purpose</th></tr></thead>';
html += '<tbody>';
html += '<tr><td class="port-req-port">7881</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">LiveKit WebRTC signalling</td></tr>';
html += '<tr><td class="port-req-port">7882</td><td class="port-req-proto">UDP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">LiveKit media (UDP mux)</td></tr>';
html += '<tr><td class="port-req-port">5349</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">TURN over TLS</td></tr>';
html += '<tr><td class="port-req-port">3478</td><td class="port-req-proto">UDP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">TURN (STUN/relay)</td></tr>';
html += '<tr><td class="port-req-port">3000040000</td><td class="port-req-proto">TCP/UDP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">TURN relay (WebRTC)</td></tr>';
html += '</tbody></table>';
html += '</div>';
// Totals
html += '<div class="onboarding-port-totals">';
html += '<strong>Total port openings: 4</strong> (without Element Calling)<br>';
html += '<strong>Total port openings: 9</strong> (with Element Calling — 4 required + 5 optional)';
html += '</div>';
html += '<div class="onboarding-port-warn" style="margin-bottom:16px;">'
+ '⚠ <strong>Ports 80 and 443 must be forwarded first.</strong> '
+ 'Caddy uses these to obtain SSL certificates from Let\'s Encrypt. '
+ 'If they are closed, HTTPS will not work and your services will be unreachable from outside your network.'
+ '</div>';
html += '<details class="onboarding-port-details" style="margin-bottom:16px;">'
+ '<summary class="onboarding-port-details-summary">How to set up port forwarding</summary>'
+ '<ol style="margin:12px 0 0 16px; padding:0; line-height:1.8;">'
+ '<li>Open your router\'s admin panel — usually <code>http://192.168.1.1</code> or <code>http://192.168.0.1</code></li>'
+ '<li>Look for <strong>"Port Forwarding"</strong>, <strong>"NAT"</strong>, or <strong>"Virtual Server"</strong> in the settings</li>'
+ '<li>Create a new rule for each port listed above</li>'
+ '<li>Set the destination/internal IP to <strong>' + ip + '</strong></li>'
+ '<li>Set both internal and external port to the same number</li>'
+ '<li>Save and apply changes</li>'
+ '</ol>'
+ '</details>';
body.innerHTML = html;
}
// ── Step 5: Complete ──────────────────────────────────────────────
async function completeOnboarding() {
var btn = document.getElementById("step-5-finish");
if (btn) { btn.disabled = true; btn.textContent = "Finishing…"; }
try {
await apiFetch("/api/onboarding/complete", { method: "POST" });
} catch (_) {
// Even if this fails, navigate to dashboard
}
window.location.href = "/";
}
// ── Event wiring ──────────────────────────────────────────────────
function wireNavButtons() {
var migrationContinue = document.getElementById("migration-password-continue");
if (migrationContinue) migrationContinue.addEventListener("click", async function() {
migrationContinue.disabled = true;
migrationContinue.textContent = "Continuing…";
setStatus("migration-password-status", "Saving acknowledgement…", "info");
try {
await apiFetch("/api/migration/password-acknowledge", { method: "POST" });
_migrationOccurred = true;
updateStep5Checklist();
showStep1FromMigration();
} catch (err) {
setStatus("migration-password-status", "⚠ " + err.message, "error");
migrationContinue.disabled = false;
migrationContinue.textContent = "I've written it down — Continue →";
}
});
// Step 1 → next
var s1next = document.getElementById("step-1-next");
if (s1next) s1next.addEventListener("click", function() { showStep(nextStep(1)); });
// Step 2 → 3 (save timezone/locale first)
var s2next = document.getElementById("step-2-next");
if (s2next) s2next.addEventListener("click", async function() {
s2next.disabled = true;
var origText = s2next.textContent;
s2next.textContent = "Saving…";
var ok = await saveStep2();
s2next.disabled = false;
s2next.textContent = origText;
if (ok) showStep(nextStep(2));
});
// Step 3 → 4 (save domains first)
var s3next = document.getElementById("step-3-next");
if (s3next) s3next.addEventListener("click", async function() {
s3next.disabled = true;
s3next.textContent = "Saving…";
await saveStep3();
s3next.disabled = false;
s3next.textContent = "Save & Continue →";
showStep(nextStep(3));
});
// Step 4 → 5 (port forwarding — no save needed)
var s4next = document.getElementById("step-4-next");
if (s4next) s4next.addEventListener("click", function() { showStep(nextStep(4)); });
// Step 5: finish
var s5finish = document.getElementById("step-5-finish");
if (s5finish) s5finish.addEventListener("click", completeOnboarding);
// Back buttons
document.querySelectorAll(".onboarding-btn-back").forEach(function(btn) {
var prev = parseInt(btn.dataset.prev, 10);
btn.addEventListener("click", function() { showStep(prevStep(prev + 1)); });
});
}
// ── Init ──────────────────────────────────────────────────────────
document.addEventListener("DOMContentLoaded", async function() {
// If onboarding is already complete, go to dashboard
try {
var status = await apiFetch("/api/onboarding/status");
if (status.complete) {
window.location.href = "/";
return;
}
} catch (_) {}
// Load role so step-skipping is applied before wiring nav buttons
try {
var cfg = await apiFetch("/api/config");
if (cfg.role) _onboardingRole = cfg.role;
} catch (_) {}
wireNavButtons();
try {
var migration = await apiFetch("/api/migration/password-status");
if (migration && migration.pending) {
updateStep5Checklist();
showMigrationStep(migration.password || "");
return;
}
} catch (_) {}
updateStep5Checklist();
showStep(1);
loadStep1();
});
@@ -1,52 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#153126"/>
<stop offset="55%" stop-color="#0F241B"/>
<stop offset="100%" stop-color="#091C14"/>
</linearGradient>
<linearGradient id="outerArc" x1="70" y1="40" x2="190" y2="210" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#42F39A"/>
<stop offset="45%" stop-color="#28D978"/>
<stop offset="100%" stop-color="#1AA45D"/>
</linearGradient>
<linearGradient id="innerArc" x1="90" y1="60" x2="180" y2="190" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#27C86F"/>
<stop offset="100%" stop-color="#157E49"/>
</linearGradient>
<filter id="innerShade" x="-10%" y="-10%" width="120%" height="120%">
<feOffset dx="0" dy="2"/>
<feGaussianBlur stdDeviation="5" result="blur"/>
<feComposite in="blur" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 .18 0"/>
</filter>
</defs>
<rect width="256" height="256" rx="48" ry="48" fill="url(#bg)"/>
<rect x="1.5" y="1.5" width="253" height="253" rx="46.5" ry="46.5"
fill="none" stroke="rgba(255,255,255,0.08)"/>
<rect x="6" y="6" width="244" height="244" rx="42" ry="42"
fill="none" filter="url(#innerShade)"/>
<path d="M128 32 A96 96 0 1 1 58 196"
fill="none"
stroke="url(#outerArc)"
stroke-width="12"
stroke-linecap="round"/>
<path d="M128 56 A72 72 0 1 1 76 178"
fill="none"
stroke="url(#innerArc)"
stroke-width="10"
stroke-linecap="round"/>
<circle cx="128" cy="128" r="8" fill="#F2FFF7"/>
<circle cx="128" cy="128" r="18" fill="none" stroke="#7BFFC0" stroke-opacity="0.14" stroke-width="4"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

-40
View File
@@ -1,40 +0,0 @@
"""Thin wrapper around the systemctl CLI for Sovran_SystemsOS_Hub."""
from __future__ import annotations
import subprocess
from typing import Literal
def _run(cmd: list[str]) -> str:
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
return result.stdout.strip()
except Exception:
return ""
def is_active(unit: str, scope: Literal["system", "user"] = "system") -> str:
return _run(["systemctl", f"--{scope}", "is-active", unit]) or "unknown"
def is_enabled(unit: str, scope: Literal["system", "user"] = "system") -> str:
return _run(["systemctl", f"--{scope}", "is-enabled", unit]) or "unknown"
def run_action(
action: str,
unit: str,
scope: Literal["system", "user"] = "system",
method: str = "systemctl",
) -> bool:
base_cmd = ["systemctl", f"--{scope}", action, unit]
if scope == "system" and method == "pkexec":
cmd = ["pkexec", "--user", "root"] + base_cmd
else:
cmd = base_cmd
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return result.returncode == 0
except Exception:
return False
@@ -1,278 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sovran_SystemsOS Hub</title>
<link rel="stylesheet" href="/static/css/base.css" />
<link rel="stylesheet" href="/static/css/buttons.css" />
<link rel="stylesheet" href="/static/css/header.css" />
<link rel="stylesheet" href="/static/css/layout.css" />
<link rel="stylesheet" href="/static/css/tiles.css" />
<link rel="stylesheet" href="/static/css/modals.css" />
<link rel="stylesheet" href="/static/css/features.css" />
<link rel="stylesheet" href="/static/css/onboarding.css" />
<link rel="stylesheet" href="/static/css/support.css" />
<link rel="stylesheet" href="/static/css/domain-setup.css" />
<link rel="stylesheet" href="/static/css/security.css" />
</head>
<body>
<!-- Header bar -->
<header class="header-bar">
<img src="/static/sovran-hub-icon.svg" alt="Sovran Hub" class="header-logo" />
<span class="title">Sovran_SystemsOS Hub</span>
<div class="header-buttons">
<span class="role-badge" id="role-badge">Loading…</span>
<button class="btn btn-logout" id="btn-logout" title="Sign out">Sign Out</button>
</div>
</header>
<!-- IP bar -->
<div class="ip-bar">
<span>
<span class="ip-label">Internal IP:</span>
<span class="ip-value" id="ip-internal"></span>
</span>
<span class="ip-separator">|</span>
<span>
<span class="ip-label">External IP:</span>
<span class="ip-value" id="ip-external"></span>
</span>
</div>
<!-- Service tiles -->
<main class="main-content">
<aside class="sidebar" id="sidebar">
<div id="sidebar-support"></div>
<div id="sidebar-features"></div>
</aside>
<div id="tiles-area"></div>
</main>
<!-- Update modal -->
<div class="modal-overlay" id="update-modal" role="dialog" aria-modal="true" aria-labelledby="modal-title-text">
<div class="modal-dialog">
<div class="modal-header">
<span class="modal-title" id="modal-title-text">Sovran_SystemsOS Update</span>
<div class="modal-spinner" id="modal-spinner"></div>
<span class="modal-status" id="modal-status">Updating…</span>
</div>
<div class="modal-log" id="modal-log" aria-live="polite"></div>
<div class="modal-footer">
<button class="btn btn-save" id="btn-save-report" style="display:none">Save Error Report</button>
<button class="btn btn-reboot" id="btn-reboot" style="display:none">Reboot</button>
<button class="btn btn-close-modal" id="btn-close-modal" disabled>Close</button>
</div>
</div>
</div>
<!-- Credentials info modal -->
<div class="modal-overlay" id="creds-modal" role="dialog" aria-modal="true" aria-labelledby="creds-modal-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title" id="creds-modal-title">Service Info</span>
<button class="creds-close-btn" id="creds-close-btn" title="Close"></button>
</div>
<div class="creds-body" id="creds-body">
<p class="creds-loading">Loading…</p>
</div>
</div>
</div>
<!-- Tech Support modal -->
<div class="modal-overlay" id="support-modal" role="dialog" aria-modal="true" aria-labelledby="support-modal-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title" id="support-modal-title">Tech Support</span>
<button class="creds-close-btn" id="support-close-btn" title="Close"></button>
</div>
<div class="creds-body" id="support-body">
<p class="creds-loading">Loading…</p>
</div>
</div>
</div>
<!-- Domain Setup Modal -->
<div class="modal-overlay" id="domain-setup-modal" role="dialog" aria-modal="true" aria-labelledby="domain-setup-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title" id="domain-setup-title">🌐 Domain Setup</span>
<button class="creds-close-btn" id="domain-setup-close-btn" title="Close"></button>
</div>
<div class="creds-body" id="domain-setup-body"></div>
</div>
</div>
<!-- SSL Email Modal -->
<div class="modal-overlay" id="ssl-email-modal" role="dialog" aria-modal="true" aria-labelledby="ssl-email-title">
<div class="creds-dialog domain-narrow-dialog">
<div class="creds-header">
<span class="creds-title" id="ssl-email-title">📧 SSL Certificate Email</span>
<button class="creds-close-btn" id="ssl-email-close-btn" title="Close"></button>
</div>
<div class="creds-body">
<p class="support-desc">Let's Encrypt needs an email address for SSL certificate notifications.</p>
<div class="domain-field-group">
<label class="domain-field-label" for="ssl-email-input">Email:</label>
<input class="domain-field-input" type="email" id="ssl-email-input" placeholder="you@example.com" />
</div>
<div class="domain-field-actions">
<button class="btn btn-close-modal" id="ssl-email-cancel-btn">Cancel</button>
<button class="btn btn-primary" id="ssl-email-save-btn">Save</button>
</div>
</div>
</div>
</div>
<!-- Feature Confirm Modal -->
<div class="modal-overlay" id="feature-confirm-modal" role="dialog" aria-modal="true" aria-labelledby="feature-confirm-title">
<div class="creds-dialog domain-narrow-dialog">
<div class="creds-header">
<span class="creds-title" id="feature-confirm-title">Confirm Action</span>
<button class="creds-close-btn" id="feature-confirm-close-btn" title="Close"></button>
</div>
<div class="creds-body">
<p class="support-desc" id="feature-confirm-message"></p>
<div class="domain-field-actions">
<button class="btn btn-close-modal" id="feature-confirm-cancel-btn">Cancel</button>
<button class="btn btn-primary" id="feature-confirm-ok-btn">Continue</button>
</div>
</div>
</div>
</div>
<!-- Port Requirements Modal -->
<div class="modal-overlay" id="port-requirements-modal" role="dialog" aria-modal="true" aria-labelledby="port-req-title">
<div class="creds-dialog">
<div class="creds-header">
<span class="creds-title" id="port-req-title">🔌 Router / Firewall Port Requirements</span>
<button class="creds-close-btn" id="port-req-close-btn" title="Close"></button>
</div>
<div class="creds-body" id="port-req-body"></div>
</div>
</div>
<!-- Rebuild Modal -->
<div class="modal-overlay" id="rebuild-modal" role="dialog" aria-modal="true" aria-labelledby="rebuild-modal-title">
<div class="modal-dialog">
<div class="modal-header">
<span class="modal-title" id="rebuild-modal-title">Sovran_SystemsOS Rebuild</span>
<div class="modal-spinner" id="rebuild-spinner"></div>
<span class="modal-status" id="rebuild-status">Rebuilding…</span>
</div>
<div class="modal-log" id="rebuild-log" aria-live="polite"></div>
<div class="modal-footer">
<button class="btn btn-save" id="rebuild-save-report" style="display:none">Save Error Report</button>
<button class="btn btn-reboot" id="rebuild-reboot-btn" style="display:none">Reboot</button>
<button class="btn btn-close-modal" id="rebuild-close-btn" disabled>Close</button>
</div>
</div>
</div>
<!-- Upgrade Modal (Node → Server+Desktop) -->
<div class="modal-overlay" id="upgrade-modal" role="dialog" aria-modal="true" aria-labelledby="upgrade-modal-title">
<div class="creds-dialog upgrade-dialog">
<div class="creds-header">
<span class="creds-title" id="upgrade-modal-title">🚀 Upgrade to Server + Desktop</span>
<button class="creds-close-btn" id="upgrade-close-btn" title="Close"></button>
</div>
<div class="creds-body">
<p class="support-desc">
Upgrading to the full <strong>Server + Desktop</strong> experience will unlock all services —
encrypted messaging, password management, cloud storage, website hosting, and more.
</p>
<div class="upgrade-info-box">
<p class="upgrade-info-title">⚠ What you should know:</p>
<ul class="upgrade-info-list">
<li>You will need to purchase domains for your services — <a href="https://njal.la" target="_blank" rel="noopener noreferrer">Njal.la</a> is the only supported domain provider</li>
<li>Some services require ports to be opened on your router</li>
</ul>
</div>
<div class="upgrade-info-box">
<p class="upgrade-info-title">️ Good to know:</p>
<ul class="upgrade-info-list">
<li>Your services will be accessible via your home internet connection. Your approximate geographic area may be visible through DNS records — this is normal for all self-hosted services</li>
<li>Your Bitcoin node remains fully private over Tor</li>
</ul>
</div>
<p class="support-desc">
<strong>Don't worry</strong> — the Hub will walk you through every step after the upgrade.
Domain setup, port forwarding, and configuration are all guided.
</p>
<p class="support-desc upgrade-rebuild-note">
The system will rebuild after upgrading. This may take several minutes.
</p>
<div class="domain-field-actions">
<button class="btn btn-close-modal" id="upgrade-cancel-btn">Cancel</button>
<button class="btn btn-primary" id="upgrade-confirm-btn">Yes, Upgrade</button>
</div>
</div>
</div>
</div>
<!-- Security Reset overlay -->
<div class="security-reset-overlay" id="security-reset-overlay">
<!-- Phase 1: wiping in progress -->
<div class="reboot-card" id="security-reset-phase1">
<div class="security-reset-overlay-icon">🛡</div>
<h2 class="reboot-title">Security Reset In Progress</h2>
<p class="reboot-message">
⚠️ Wiping all data and credentials.<br />
<strong>Do not power off your computer.</strong><br />
This may take several minutes.
</p>
<div class="reboot-dots">
<span class="reboot-dot"></span>
<span class="reboot-dot"></span>
<span class="reboot-dot"></span>
</div>
<p class="reboot-submessage" id="security-reset-overlay-step">Erasing data and resetting credentials…</p>
</div>
<!-- Phase 2: password display -->
<div class="reboot-card" id="security-reset-phase2" style="display:none;">
<div class="security-reset-overlay-icon">🔑</div>
<h2 class="reboot-title">Security Reset Complete</h2>
<p class="security-reset-password-label">Your new login password is:</p>
<div class="security-reset-password-box" id="security-reset-new-password">&nbsp;</div>
<p class="security-reset-password-warning">
✍️ <strong>Write this down now.</strong><br />
You will need it to log in to your computer<br />and the Sovran Hub at <em>sovransystemsos.local</em>.
</p>
<button class="security-reset-reboot-btn" id="security-reset-reboot-btn" disabled>
I have written down my new password — Reboot now
</button>
</div>
</div>
<!-- Reboot overlay -->
<div class="reboot-overlay" id="reboot-overlay">
<div class="reboot-card">
<div class="reboot-icon"></div>
<h2 class="reboot-title">System Rebooting</h2>
<p class="reboot-message">
Sovran_SystemsOS is now restarting.<br />
This page will automatically reconnect once the system is back online.
</p>
<div class="reboot-dots">
<span class="reboot-dot"></span>
<span class="reboot-dot"></span>
<span class="reboot-dot"></span>
</div>
<p class="reboot-submessage">Stay tuned…</p>
</div>
</div>
<script src="/static/js/constants.js"></script>
<script src="/static/js/state.js"></script>
<script src="/static/js/helpers.js"></script>
<script src="/static/js/tiles.js"></script>
<script src="/static/js/service-detail.js"></script>
<script src="/static/js/support.js"></script>
<script src="/static/js/update.js"></script>
<script src="/static/js/rebuild.js"></script>
<script src="/static/js/features.js"></script>
<script src="/static/js/security.js"></script>
<script src="/static/js/events.js"></script>
</body>
</html>
@@ -1,83 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sovran Hub — Login</title>
<link rel="stylesheet" href="/static/css/base.css" />
<link rel="stylesheet" href="/static/css/buttons.css" />
</head>
<body>
<div class="login-wrapper">
<div class="login-card">
<div class="login-header">
<img src="/static/sovran-hub-icon.svg" alt="Sovran Hub" class="login-logo" />
<div class="login-title">Sovran Hub</div>
</div>
<form class="login-form" id="login-form" onsubmit="return false;">
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
autocomplete="current-password"
autofocus
placeholder="Enter your Hub password"
/>
</div>
<div class="login-error" id="login-error">Incorrect password. Please try again.</div>
<button type="submit" class="btn btn-login" id="btn-login">Sign In</button>
</form>
</div>
</div>
<script>
(function () {
var form = document.getElementById('login-form');
var input = document.getElementById('password');
var errEl = document.getElementById('login-error');
var btnEl = document.getElementById('btn-login');
form.addEventListener('submit', function () {
var password = input.value;
if (!password) return;
btnEl.disabled = true;
btnEl.textContent = 'Signing in…';
errEl.classList.remove('visible');
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: password }),
credentials: 'same-origin',
})
.then(function (res) {
if (res.ok) {
window.location.replace('/');
} else {
return res.json().then(function (data) {
errEl.textContent = (data && data.detail) ? data.detail : 'Incorrect password. Please try again.';
errEl.classList.add('visible');
input.value = '';
input.focus();
btnEl.disabled = false;
btnEl.textContent = 'Sign In';
});
}
})
.catch(function () {
errEl.textContent = 'Network error. Please try again.';
errEl.classList.add('visible');
btnEl.disabled = false;
btnEl.textContent = 'Sign In';
});
});
})();
</script>
</body>
</html>
@@ -1,200 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sovran_SystemsOS — First-Boot Setup</title>
<link rel="stylesheet" href="/static/css/base.css" />
<link rel="stylesheet" href="/static/css/buttons.css" />
<link rel="stylesheet" href="/static/css/header.css" />
<link rel="stylesheet" href="/static/css/layout.css" />
<link rel="stylesheet" href="/static/css/tiles.css" />
<link rel="stylesheet" href="/static/css/modals.css" />
<link rel="stylesheet" href="/static/css/features.css" />
<link rel="stylesheet" href="/static/css/onboarding.css" />
<link rel="stylesheet" href="/static/css/support.css" />
<link rel="stylesheet" href="/static/css/domain-setup.css" />
</head>
<body class="onboarding-body">
<!-- Onboarding wizard container -->
<div class="onboarding-shell">
<!-- Progress bar -->
<div class="onboarding-progress-bar" id="onboarding-progress-bar">
<div class="onboarding-progress-fill" id="onboarding-progress-fill"></div>
</div>
<!-- Step indicators -->
<div class="onboarding-steps-nav" id="onboarding-steps-nav">
<span class="onboarding-step-dot" data-step="1">1</span>
<span class="onboarding-step-connector"></span>
<span class="onboarding-step-dot" data-step="2">2</span>
<span class="onboarding-step-connector"></span>
<span class="onboarding-step-dot" data-step="3">3</span>
<span class="onboarding-step-connector"></span>
<span class="onboarding-step-dot" data-step="4">4</span>
<span class="onboarding-step-connector"></span>
<span class="onboarding-step-dot" data-step="5">5</span>
</div>
<!-- Step panels -->
<div class="onboarding-panel-wrap">
<!-- ── Migration Password Gate (pre-step) ── -->
<div class="onboarding-panel" id="step-migration" style="display:none">
<div class="onboarding-hero">
<div class="onboarding-logo">🔐</div>
<h1 class="onboarding-title">Your system has been migrated to Sovran_SystemsOS</h1>
<p class="onboarding-subtitle">Important password update required</p>
</div>
<div class="onboarding-card">
<p class="onboarding-body-text" style="text-align:center; margin-bottom:4px;">
Your new login password is:
</p>
<div id="migration-password-value" style="font-family:monospace; font-size:1.35rem; font-weight:700; color:var(--text-primary); background:rgba(109, 191, 139, 0.10); border:1.5px solid rgba(109, 191, 139, 0.35); border-radius:8px; padding:14px 24px; letter-spacing:0.04em; text-align:center; word-break:break-all; margin-bottom:8px;">
&nbsp;
</div>
<div style="padding:10px 14px; background-color:rgba(229, 165, 10, 0.1); border:1px solid rgba(229, 165, 10, 0.35); border-radius:8px; font-size:0.92rem; color:var(--yellow); line-height:1.55;">
⚠ Write this password down! You will need it to log in next time. This is also your Sovran Hub login password.
</div>
<div id="migration-password-status" class="onboarding-save-status" style="margin-top:8px;"></div>
</div>
<div class="onboarding-footer">
<div></div>
<button class="btn btn-primary" id="migration-password-continue">
I've written it down — Continue →
</button>
</div>
</div>
<!-- ── Step 1: Welcome ── -->
<div class="onboarding-panel" id="step-1">
<div class="onboarding-hero">
<div class="onboarding-logo">
<img src="/static/logo-light.svg" alt="Sovran Systems" class="onboarding-logo-img" />
</div>
<h1 class="onboarding-title">Welcome to Sovran_SystemsOS!</h1>
<p class="onboarding-subtitle">Be Digitally Sovereign</p>
</div>
<div class="onboarding-card">
<p class="onboarding-body-text">
Your system is installed and ready to configure. This wizard will guide
you through the final setup steps so everything works perfectly.
</p>
<div class="onboarding-role-row" id="onboarding-role-row">
<span class="onboarding-role-label">Your Role:</span>
<span class="onboarding-role-badge" id="onboarding-role-badge">Loading…</span>
</div>
<p class="onboarding-body-text onboarding-body-text--dim">
This setup only takes a few minutes. You can always revisit these
settings from the main Hub dashboard.
</p>
</div>
<div class="onboarding-footer">
<div></div>
<button class="btn btn-primary onboarding-btn-next" id="step-1-next">
Let's Go →
</button>
</div>
</div>
<!-- ── Step 2: Timezone &amp; Locale ── -->
<div class="onboarding-panel" id="step-2" style="display:none">
<div class="onboarding-step-header">
<span class="onboarding-step-icon">🌍</span>
<h2 class="onboarding-step-title">Timezone &amp; Locale</h2>
<p class="onboarding-step-desc">
Select your timezone and preferred language so your system clock, logs,
and services display the correct time and format.
</p>
</div>
<div class="onboarding-card" id="step-2-body">
<p class="onboarding-loading">Loading timezone data…</p>
</div>
<div id="step-2-status" class="onboarding-save-status"></div>
<div class="onboarding-footer">
<button class="btn btn-close-modal onboarding-btn-back" data-prev="1">← Back</button>
<button class="btn btn-primary onboarding-btn-next" id="step-2-next">
Save &amp; Continue →
</button>
</div>
</div>
<!-- ── Step 3: Domain Configuration ── -->
<div class="onboarding-panel" id="step-3" style="display:none">
<div class="onboarding-step-header">
<span class="onboarding-step-icon">🌐</span>
<h2 class="onboarding-step-title">Domain Configuration</h2>
<p class="onboarding-step-desc">
Sovran_SystemsOS uses <strong><a href="https://njal.la" target="_blank" style="color: var(--accent-color);">Njal.la</a></strong> for domains and Dynamic DNS.
First, create an account at <strong>Njal.la</strong> and purchase a new domain, or create a subdomain from a domain you already own. Tip: Subdomains are free to create — you only need to purchase one domain, and you can add as many subdomains as you need at no extra cost.
Then, in the Njal.la web interface, create a <strong>Dynamic</strong> record pointing to this machine's external IP address (shown below).
Finally, paste the DDNS curl command from your Njal.la dashboard for each service below.
</p>
</div>
<div class="onboarding-card" id="step-3-body">
<p class="onboarding-loading">Loading service information…</p>
</div>
<div id="step-3-status" class="onboarding-save-status"></div>
<div class="onboarding-footer">
<button class="btn btn-close-modal onboarding-btn-back" data-prev="2">← Back</button>
<button class="btn btn-primary onboarding-btn-next" id="step-3-next">
Save &amp; Continue →
</button>
</div>
</div>
<!-- ── Step 4: Port Forwarding ── -->
<div class="onboarding-panel" id="step-4" style="display:none">
<div class="onboarding-step-header">
<span class="onboarding-step-icon">🔌</span>
<h2 class="onboarding-step-title">Port Forwarding Check</h2>
<p class="onboarding-step-desc">
Forward these ports on your router to this machine. Each port only needs to be opened once — they are shared across all your services.
<strong>Ports 80 and 443 must be open for SSL certificates to work.</strong>
</p>
</div>
<div class="onboarding-card" id="step-4-body">
<p class="onboarding-loading">Checking ports…</p>
</div>
<div class="onboarding-footer">
<button class="btn btn-close-modal onboarding-btn-back" data-prev="3">← Back</button>
<button class="btn btn-primary onboarding-btn-next" id="step-4-next">
Continue →
</button>
</div>
</div>
<!-- ── Step 5: Complete ── -->
<div class="onboarding-panel" id="step-5" style="display:none">
<div class="onboarding-hero">
<div class="onboarding-logo"></div>
<h1 class="onboarding-title">Your Sovran_SystemsOS is Ready!</h1>
<p class="onboarding-subtitle">Setup complete</p>
</div>
<div class="onboarding-card">
<p class="onboarding-body-text">
All configuration steps are done. Head to the main Hub dashboard to
monitor your services, manage credentials, and make changes at any time.
</p>
<ul class="onboarding-checklist" id="onboarding-checklist">
<li>✅ Timezone &amp; locale configured</li>
<li>✅ Domain configuration saved</li>
<li>✅ Port forwarding reviewed</li>
</ul>
</div>
<div class="onboarding-footer">
<button class="btn btn-close-modal onboarding-btn-back" data-prev="4">← Back</button>
<button class="btn btn-primary" id="step-5-finish">
Go to Dashboard →
</button>
</div>
</div>
</div><!-- /panel-wrap -->
</div><!-- /shell -->
<script src="/static/onboarding.js?v={{ onboarding_js_hash }}"></script>
</body>
</html>
@@ -1,300 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 3440 1440"
width="3440"
height="1440"
version="1.1"
id="svg21"
sodipodi:docname="sovran-wallpaper-12-ultrawide-3440x1440.svg"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
xml:space="preserve"
inkscape:export-filename="sovran-wallpaper-12-ultrawide-3440x1440.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview21"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.657762"
inkscape:cx="1721.7474"
inkscape:cy="718.34493"
inkscape:window-width="3440"
inkscape:window-height="1363"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg21" /><defs
id="defs14"><linearGradient
id="bg"
x1="0"
y1="0"
x2="1"
y2="1"><stop
offset="0%"
stop-color="#040706"
id="stop1" /><stop
offset="50%"
stop-color="#06100c"
id="stop2" /><stop
offset="100%"
stop-color="#050706"
id="stop3" /></linearGradient><radialGradient
id="softGlow"
cx="0"
cy="0"
r="210"
fx="0"
fy="0"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(210)"><stop
offset="0%"
stop-color="#28d978"
stop-opacity="0.04"
id="stop4" /><stop
offset="100%"
stop-color="#28d978"
stop-opacity="0"
id="stop5" /></radialGradient><linearGradient
id="tileBg"
x1="0"
y1="0"
x2="0"
y2="340"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(210)"><stop
offset="0%"
stop-color="#153126"
id="stop6" /><stop
offset="55%"
stop-color="#0F241B"
id="stop7" /><stop
offset="100%"
stop-color="#091C14"
id="stop8" /></linearGradient><linearGradient
id="outerArc"
x1="75.634857"
y1="47.268153"
x2="326.94922"
y2="298.58251"
gradientTransform="matrix(0.95194204,0,0,1.0504841,210,0)"
gradientUnits="userSpaceOnUse"><stop
offset="0%"
stop-color="#42F39A"
id="stop9" /><stop
offset="45%"
stop-color="#28D978"
id="stop10" /><stop
offset="100%"
stop-color="#1AA45D"
id="stop11" /></linearGradient><linearGradient
id="innerArc"
x1="130.64136"
y1="106.15404"
x2="258.09194"
y2="251.81184"
gradientTransform="matrix(0.95325178,0,0,1.0490408,210,0)"
gradientUnits="userSpaceOnUse"><stop
offset="0%"
stop-color="#27C86F"
id="stop12" /><stop
offset="100%"
stop-color="#157E49"
id="stop13" /></linearGradient><filter
id="tileShadow"
x="-0.12705882"
y="-0.12705882"
width="1.2541176"
height="1.2952941"><feOffset
dy="14"
id="feOffset13" /><feGaussianBlur
stdDeviation="18"
result="blur"
id="feGaussianBlur13" /><feColorMatrix
type="matrix"
values=" 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .24 0"
id="feColorMatrix13" /><feMerge
id="feMerge14"><feMergeNode
in="blur"
id="feMergeNode13" /><feMergeNode
in="SourceGraphic"
id="feMergeNode14" /></feMerge></filter><linearGradient
id="bg-7"
x1="0"
y1="0"
x2="0"
y2="256"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.79478947,0,0,0.82005964,452.62858,4.2254746)"><stop
offset="0%"
stop-color="#153126"
id="stop1-0" /><stop
offset="55%"
stop-color="#0F241B"
id="stop2-9" /><stop
offset="100%"
stop-color="#091C14"
id="stop3-3" /></linearGradient><linearGradient
id="outerArc-6"
x1="70"
y1="40"
x2="190"
y2="210"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.79478947,0,0,0.82005964,452.62858,4.2254746)"><stop
offset="0%"
stop-color="#42F39A"
id="stop4-0" /><stop
offset="45%"
stop-color="#28D978"
id="stop5-6" /><stop
offset="100%"
stop-color="#1AA45D"
id="stop6-2" /></linearGradient><linearGradient
id="innerArc-6"
x1="90"
y1="60"
x2="180"
y2="190"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.79478947,0,0,0.82005964,452.62858,4.2254746)"><stop
offset="0%"
stop-color="#27C86F"
id="stop7-1" /><stop
offset="100%"
stop-color="#157E49"
id="stop8-8" /></linearGradient><filter
id="innerShade"
x="-0.049180328"
y="-0.049180328"
width="1.0983607"
height="1.1065574"><feOffset
dx="0"
dy="2"
id="feOffset8" /><feGaussianBlur
stdDeviation="5"
result="blur"
id="feGaussianBlur8" /><feComposite
in="blur"
in2="SourceAlpha"
operator="arithmetic"
k2="-1"
k3="1"
id="feComposite8" /><feColorMatrix
type="matrix"
values=" 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .18 0"
id="feColorMatrix8" /></filter></defs><rect
width="3440"
height="1440"
fill="url(#bg)"
id="rect14" /><!-- centered for ultrawide balance --><g
id="g3"
transform="translate(18.243681,41.048282)"><g
id="g2"
transform="translate(-161.15251,-21.284294)"><g
transform="translate(1330,720)"
id="g20"><circle
cx="0"
cy="0"
r="310"
fill="none"
stroke="rgba(242,255,247,0.045)"
stroke-width="1"
id="circle15" /><circle
cx="0"
cy="0"
r="390"
fill="none"
stroke="rgba(66,243,154,0.055)"
stroke-width="2"
stroke-dasharray="3, 22"
id="circle16" /></g><g
transform="translate(1565,702)"
id="g21"><text
x="0"
y="0"
fill="#c3cbc6"
font-family="Inter, ui-sans-serif, system-ui, '-apple-system', BlinkMacSystemFont, 'Segoe UI', sans-serif"
font-size="42px"
font-weight="500"
letter-spacing="8"
id="text20">PRIVACY. SOVEREIGNTY. BITCOIN.</text><rect
x="0"
y="56"
width="240"
height="2"
rx="1"
fill="#42f39a"
id="rect21" /></g><g
id="g1"
transform="matrix(1.2581949,0,0,1.2194235,1167.4138,564.08337)"><rect
width="256"
height="256"
rx="48"
ry="48"
fill="url(#bg)"
id="rect8"
style="fill:url(#bg-7)"
x="0"
y="0" /><rect
x="1.5"
y="1.5"
width="253"
height="253"
rx="46.5"
ry="46.5"
fill="none"
stroke="rgba(255,255,255,0.08)"
id="rect9" /><rect
x="6"
y="6"
width="244"
height="244"
rx="42"
ry="42"
fill="none"
filter="url(#innerShade)"
id="rect10" /><path
d="M 128,32 A 96,96 0 1 1 58,196"
fill="none"
stroke="url(#outerArc)"
stroke-width="12"
stroke-linecap="round"
id="path10"
style="stroke:url(#outerArc-6)" /><path
d="M 128,56 A 72,72 0 1 1 76,178"
fill="none"
stroke="url(#innerArc)"
stroke-width="10"
stroke-linecap="round"
id="path11"
style="stroke:url(#innerArc-6)" /><circle
cx="128"
cy="128"
r="8"
fill="#f2fff7"
id="circle11" /><circle
cx="128"
cy="128"
r="18"
fill="none"
stroke="#7bffc0"
stroke-opacity="0.14"
stroke-width="4"
id="circle12" /></g><rect
x="0"
y="56"
width="560"
height="2"
rx="1"
fill="rgba(242,255,247,0.08)"
id="rect20"
style="fill:#b3b3b3"
transform="translate(1565,702)" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 8.8 KiB

Regular → Executable
+289 -92
View File
@@ -1,19 +1,29 @@
{ config, pkgs, lib, ... }:
let
personalization = import ./modules/personalization.nix;
in
{
imports = [
imports =
[
./modules/modules.nix
];
# ── Boot ────────────────────────────────────────────────────
# Bootloader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.efi.efiSysMountPoint = "/boot/efi";
boot.kernelPackages = pkgs.linuxPackages_latest;
boot.kernelParams = [ "quiet" "loglevel=3" "rd.systemd.show_status=false" "udev.log_level=3" ];
boot.blacklistedKernelModules = [ "rxrpc" ];
# ── Filesystems ─────────────────────────────────────────────
# Enable Automount without Fail for Internal Drive.
fileSystems."/run/media/Second_Drive" = {
device = "LABEL=BTCEcoandBackup";
fsType = "ext4";
@@ -22,66 +32,44 @@
fileSystems."/boot/efi".options = [ "umask=0077" "defaults" ];
# ── Nix Settings ────────────────────────────────────────────
nix.settings = {
experimental-features = [ "nix-command" "flakes" ];
download-buffer-size = 524288000;
};
# ── Networking ──────────────────────────────────────────────
networking.hostName = "nixos";
networking.hostName = "nixos"; # Define your hostname.
# Enable networking
networking.networkmanager.enable = true;
networking.firewall.enable = true;
networking.firewall.allowedUDPPorts = [ 5353 ];
# ── Avahi (mDNS) ───────────────────────────────────────────
services.avahi = {
enable = true;
hostName = "sovransystemsos";
nssmdns4 = true;
publish = { enable = true; addresses = true; };
# Set your time zone.
time.timeZone = "America/Los_Angeles";
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
# Enable the X11 windowing system.
services.xserver.enable = true;
# Enable the GNOME Desktop Environment.
services.displayManager.gdm.enable = true;
services.desktopManager.gnome.enable = true;
# Configure keymap in X11
services.xserver.xkb = {
layout = "us";
variant = "";
};
# ── Locale / Time ──────────────────────────────────────────
time.timeZone = null;
i18n.defaultLocale = lib.mkDefault "en_US.UTF-8";
i18n.supportedLocales = [
"en_US.UTF-8/UTF-8"
"en_GB.UTF-8/UTF-8"
"es_ES.UTF-8/UTF-8"
"fr_FR.UTF-8/UTF-8"
"de_DE.UTF-8/UTF-8"
"pt_BR.UTF-8/UTF-8"
"ja_JP.UTF-8/UTF-8"
"zh_CN.UTF-8/UTF-8"
"ko_KR.UTF-8/UTF-8"
"ru_RU.UTF-8/UTF-8"
"ar_SA.UTF-8/UTF-8"
"hi_IN/UTF-8"
];
# ── Desktop ────────────────────────────────────────────────
services.displayManager.gdm.enable = true;
services.displayManager.gdm.autoSuspend = false;
services.displayManager.gdm.wayland = true;
services.desktopManager.gnome.enable = true;
# Enable CUPS to print documents.
services.printing.enable = true;
# Systemd Settings
systemd.enableEmergencyMode = false;
environment.gnome.excludePackages = [ pkgs.gnome-tour ];
security.pam.services.gdm-password.enableGnomeKeyring = true;
security.pam.services.gdm-autologin.enableGnomeKeyring = true;
# Declaratively guarantee the GNOME Keyring default pointer exists.
# Defining the full path ensures root doesn't accidentally lock the user out of .local
systemd.tmpfiles.rules = [
"d /home/free/.local 0700 free users -"
"d /home/free/.local/share 0700 free users -"
"d /home/free/.local/share/keyrings 0700 free users -"
"f /home/free/.local/share/keyrings/default 0600 free users - login\n"
];
# ── Audio ──────────────────────────────────────────────────
# Enable sound with pipewire.
services.pulseaudio.enable = false;
security.rtkit.enable = true;
services.pipewire = {
@@ -91,83 +79,237 @@
pulse.enable = true;
};
# ── Users ──────────────────────────────────────────────────
users.users.free = {
users.users = {
free = {
isNormalUser = true;
description = "free";
extraGroups = [ "networkmanager" ];
};
};
services.displayManager.autoLogin.enable = false;
# Enable automatic login for the user.
services.displayManager.autoLogin.enable = true;
services.displayManager.autoLogin.user = "free";
# ── Flatpak ────────────────────────────────────────────────
# Allow Flatpak
services.flatpak.enable = true;
systemd.services.flatpak-repo = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" "nss-lookup.target" ];
wants = [ "network-online.target" "nss-lookup.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = [ pkgs.flatpak ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
Restart = "on-failure";
RestartSec = "15s";
};
unitConfig = {
StartLimitIntervalSec = 120;
StartLimitBurst = 5;
};
script = ''
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
'';
};
# ── Packages ───────────────────────────────────────────────
# Allow unfree packages
nixpkgs.config.allowUnfree = true;
nixpkgs.config.permittedInsecurePackages = [
"jitsi-meet-1.0.8043"
];
# List packages installed
environment.systemPackages = with pkgs; [
nftables
git wget fish htop btop
git
wget
fish
htop
btop
gnomeExtensions.transparent-top-bar-adjustable-transparency
gnomeExtensions.systemd-manager
gnomeExtensions.dash-to-dock
gnomeExtensions.vitals
gnomeExtensions.pop-shell
gnomeExtensions.just-perfection
gnomeExtensions.appindicator
gnomeExtensions.date-menu-formatter
gnome-tweaks papirus-icon-theme
ranger fastfetch gedit openssl pwgen
aspell aspellDicts.en lm_sensors
hunspell hunspellDicts.en_US
synadm brave dua bitwarden-desktop
gparted pv unzip parted screen zenity
libargon2 gnome-terminal libreoffice-fresh
dig firefox element-desktop wp-cli axel
lk-jwt-service livekit-libwebrtc livekit-cli livekit
matrix-synapse age
gnome-tweaks
papirus-icon-theme
ranger
fastfetch
gedit
matrix-synapse
openssl
pwgen
aspell
aspellDicts.en
lm_sensors
hunspell
hunspellDicts.en_US
synadm
brave
dua
bitwarden-desktop
gparted
pv
unzip
parted
screen
zenity
libargon2
gnome-terminal
libreoffice-fresh
dig
firefox
element-desktop
wp-cli
axel
lk-jwt-service
livekit-libwebrtc
livekit-cli
livekit
];
# ── Shell ──────────────────────────────────────────────────
programs.nixvim = {
enable = true;
colorschemes.catppuccin.enable = true;
plugins.lualine.enable = true;
};
programs.bash.promptInit = "fish";
programs.fish = { enable = true; promptInit = "fastfetch"; };
# ── PostgreSQL base ────────────────────────────────────────
programs.bash.promptInit = "fish";
programs.fish = {
enable = true;
promptInit = "fastfetch";
};
####### CADDY #######
services.caddy = {
enable = true;
user = "caddy";
group = "root";
email = "${personalization.caddy_email_for_acme}";
virtualHosts = {
"${personalization.wordpress_url}" = {
extraConfig = ''
encode gzip zstd
root * /var/lib/www/wordpress
php_fastcgi unix//run/phpfpm/mypool.sock
file_server browse
'';
};
"${personalization.nextcloud_url}" = {
extraConfig = ''
encode gzip zstd
root * /var/lib/www/nextcloud
php_fastcgi unix//run/phpfpm/mypool.sock {
trusted_proxies private_ranges
}
file_server
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
header {
Strict-Transport-Security max-age=31536000;
}
'';
};
"${personalization.matrix_url}" = {
extraConfig = ''
reverse_proxy /_matrix/* http://localhost:8008
reverse_proxy /_synapse/client/* http://localhost:8008
'';
};
"${personalization.matrix_url}:8448" = {
extraConfig = ''
reverse_proxy http://localhost:8008
'';
};
"${personalization.btcpayserver_url}" = {
extraConfig = ''
reverse_proxy http://localhost:23000
encode gzip zstd
'';
};
"https://${personalization.vaultwarden_url}" = {
extraConfig = ''
reverse_proxy http://localhost:8777
encode gzip zstd
'';
};
":3051" = {
extraConfig = ''
reverse_proxy :3050
encode gzip zstd
'';
};
};
};
###### AGENIX ######
age.identityPaths = [ "/root/.ssh/agenix/agenix-secret-keys" ];
age.secrets.matrix_reg_secret = {
file = /var/lib/agenix-secrets/matrix_reg_secret.age;
mode = "770";
owner = "matrix-synapse";
group = "matrix-synapse";
};
###### CREATE DATABASE (WORDPRESS, MATRIX_SYNAPSE, AND NEXTCLOUD) #######
services.postgresql = {
enable = true;
authentication = lib.mkForce ''
};
services.postgresql.authentication = lib.mkForce ''
# Generated file; do not edit!
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
'';
services.mysql = {
enable = true;
package = pkgs.mariadb;
};
# ── Backups ────────────────────────────────────────────────
services.postgresql.initialScript = pkgs.writeText "begin-init.sql" ''
CREATE ROLE "ncusr" WITH LOGIN PASSWORD '${personalization.nextclouddb}';
CREATE DATABASE "nextclouddb" WITH OWNER "ncusr"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD '${personalization.matrixdb}';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
''
;
services.mysql.initialScript = pkgs.writeText "wordpress-init.sql" ''
CREATE DATABASE wordpressdb;
CREATE USER 'wpusr'@'localhost' IDENTIFIED BY '${personalization.wordpressdb}';
GRANT ALL ON wordpressdb.* TO 'wpusr'@'localhost';
FLUSH PRIVILEGES;
''
;
####### KEEP AWAKE for DISPLAY and HEADLESS #######
services.displayManager.gdm.autoSuspend = false;
####### BACKUP TO INTERNAL DRIVE #######
services.rsnapshot = {
enable = true;
extraConfig = ''
@@ -185,19 +327,74 @@ backup /etc/nix-bitcoin-secrets/ localhost/
};
};
# ── Cron ───────────────────────────────────────────────────
####### CRON #######
services.cron = {
enable = true;
systemCronJobs = [
"*/5 * * * * caddy /run/current-system/sw/bin/php -f /var/lib/www/nextcloud/cron.php"
"*/15 * * * * root /run/current-system/sw/bin/bash /var/lib/njalla/njalla.sh"
"*/15 * * * * root /run/current-system/sw/bin/bash /var/lib/external_ip/external_ip.sh"
"0 0 * * 0 docker-user yes | /run/current-system/sw/bin/docker system prune -a"
];
};
# ── Tor ────────────────────────────────────────────────────
services.tor = { enable = true; client.enable = true; torsocks.enable = true; };
# ── Garbage Collection ─────────────────────────────────────
nix.gc = { automatic = true; dates = "weekly"; options = "--delete-older-than 7d"; };
####### TOR #######
services.tor = {
enable = true;
client.enable = true;
torsocks.enable = true;
};
services.privoxy.enableTor = true;
####### Enable the SSH #######
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "yes";
};
};
#######FailtoBan#######
services.fail2ban = {
enable = true;
ignoreIP = [
"127.0.0.0/8"
"10.0.0.0/8"
"172.16.0.0/12"
"192.168.0.0/16"
"8.8.8.8"
];
};
####### Open ports in the firewall #######
networking.firewall.allowedTCPPorts = [ 80 443 5349 8448 3051 ];
networking.firewall.allowedUDPPorts = [ 80 443 5349 8448 3051 ];
networking.firewall.allowedUDPPortRanges = [
{ from=49152; to=65535; } # TURN relay
];
networking.firewall.enable = true;
####### AUTO COLLECT GARABAGE #######
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
system.stateVersion = "22.05";
}
+124
View File
@@ -0,0 +1,124 @@
## Custom Add-ons for Sovran_SystemsOS and The Sovran Pro
Add-ons are extra features you can have enabled before your Sovran Pro is shipped to you or you can enable them yourself.
## The information about each Feature
1. Since Sovran_SystemsOS runs Bitcoin Knots by default as opposed to Bitcion Core, you can customize your Sovran Pro's Bitcoin node to run Bitcoin Core.
https://github.com/bitcoin/bitcoin
2. BIP-110 keeps Bitcoin more efficient as Peer to Peer Cash and you can run it along side your Bitocoin node.
https://github.com/bitcoin/bips/blob/master/bip-0110.mediawiki
3. The Bitcoin Mempool can be added and can be accessed via Tor or on your local network.
https://github.com/mempool/mempool
4. The Haven Relay for NOSTR (NOTES AND OTHER STUFF TRANSMITED BY RELAYS) is a Decenterized Social Media/File Sharing.
https://github.com/barrydeen/haven
5. You can run the new Element Voice and Video calling backend.
https://github.com/element-hq/element-call
6. You can run the Gnome Remote Desktop to view your desktop from another computer in the nextwork.
https://gitlab.gnome.org/GNOME/gnome-remote-desktop
---
## The DIY for each Feature
All code belongs in the `custom.nix` file located at `/etc/nixos/custom.nix`.
If you would like to enable these features yourself after you have received your Sovran Pro, then open the *terminal* app and type or paste in
```bash
ssh root@localhost
```
Type in the password in the diaolog box if necessary. It is the same password to run the Sovran_Systems_Updater app.
Then press enter.
Next, type or paste in
```bash
nano /etc/nixos/custom.nix
```
Then press enter.
Next type or paste the codes below *(Code for each Feature)* each on their own line into the termainl/nano window right above the last `}`
Once done, press `ctr s` then `ctr x` to save and exit.
Last, type or paste in
```bash
nixos-rebuild switch --impure
```
Then press enter.
After it is done bulding, reboot your Sovran Pro typeing or pasting in
```bash
reboot
```
---
## The code for each Feature (All Features are disabled by default)
1. The code to enable Bitcoin Core is as follows:
```nix
sovran_systemsOS.features.bitcoin-core = lib.mkForce true;
```
2. The code to enable BIP-110 is as follows:
```nix
sovran_systemsOS.features.bip110 = lib.mkForce true;
```
3. The code to enable Mempool is as follows:
```nix
sovran_systemsOS.features.mempool = lib.mkForce true;
```
4. The code to enable Haven Relay is as follows (also Haven will need a new domain to work):
```nix
sovran_systemsOS.features.haven = lib.mkForce true;
sovran_systemsOS.nostr_npub = "pasteyournpubhere";
```
5. The code to enable Element Calling is as follows (also Element Calling will need a new domain to work):
```nix
sovran_systemsOS.features.element-calling = lib.mkForce true;
```
6. The code to enable Gnome Remote Desktop is as follows:
```nix
sovran_systemsOS.features.rdp = lib.mkForce true;
```
Next, in a open the terminal app and in the new window paste this in:
```bash
ssh root@localhost
```
Press enter
Type in the password if required. It will be the same password to run the Sovran_SystemsOS_Updater app.
Last, paste in this command to see the log in information to log in from any RDP client software (i.e. Remmina) from any computer on your home network
```bash
cat /var/lib/gnome-remote-desktop/rdp-credentials
```
-41
View File
@@ -1,41 +0,0 @@
{ config, lib, ... }:
{
###########################################################
# #
# Sovran_SystemsOS — custom.nix #
# #
# Services, features, and roles are managed by the #
# Sovran Hub. Any changes you make through the Hub #
# will appear in a Hub Managed section added #
# automatically below. #
# #
# If you want to add your own NixOS modules or #
# configuration, place them here — outside of the #
# Hub Managed section. #
# #
# After making changes, rebuild with: #
# #
# nixos-rebuild switch #
# #
###########################################################
# ─── Add your custom NixOS configuration below ───────────
# ─── Custom Caddy virtual hosts ──────────────────────────
# Uncomment and edit below to add your own Caddy sites:
#
# sovran_systemsOS.caddy.extraVirtualHosts = ''
# mysite.example.com {
# encode gzip zstd
# root * /var/lib/www/mysite
# php_fastcgi unix//run/phpfpm/mypool.sock
# file_server browse
# }
#
# anotherdomain.com {
# reverse_proxy localhost:9090
# }
# '';
}
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
cd /home/free/Downloads
#### SCRIPT 1 ####
/run/current-system/sw/bin/wget "https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/sovran-pro-flake-update.sh"
/run/current-system/sw/bin/bash /home/free/Downloads/sovran-pro-flake-update.sh
rm -rf /home/free/Downloads/sovran-pro-flake-update.sh
#### SCRIPT 2 ####
/run/current-system/sw/bin/wget "https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/add-custom-nix.sh"
/run/current-system/sw/bin/bash /home/free/Downloads/add-custom-nix.sh
rm -rf /home/free/Downloads/add-custom-nix.sh
#### SCRIPT 3 ####
/run/current-system/sw/bin/wget "https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/sovran-pro-flake-update2.sh"
/run/current-system/sw/bin/bash /home/free/Downloads/sovran-pro-flake-update2.sh
rm -rf /home/free/Downloads/sovran-pro-flake-update2.sh
#### SCRIPT 4 ####
/run/current-system/sw/bin/wget "https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/nextcloud_maintenance_window_fix.sh"
/run/current-system/sw/bin/bash /home/free/Downloads/nextcloud_maintenance_window_fix.sh
rm -rf /home/free/Downloads/nextcloud_maintenance_window_fix.sh
#### SCRIPT 5 ####
/run/current-system/sw/bin/wget "https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/add_external_backup_app.sh"
/run/current-system/sw/bin/bash /home/free/Downloads/add_external_backup_app.sh
rm -rf /home/free/Downloads/add_external_backup_app.sh
#### SCRIPT 6 ####
/run/current-system/sw/bin/wget "https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/update-agenix.sh"
/run/current-system/sw/bin/bash /home/free/Downloads/update-agenix.sh
rm -rf /home/free/Downloads/update-agenix.sh
#### SCRIPT 7 ####
/run/current-system/sw/bin/wget "https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/element-calling_haven"
/run/current-system/sw/bin/bash /home/free/Downloads/element-calling_haven.sh
rm -rf /home/free/Downloads/element-calling_haven.sh
#### REMOVAL OF MAIN SCRIPT ####
rm -rf /home/free/Downloads/Sovran_SystemsOS_File_Fixes_And_New_Services.sh
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
function log_console () {
echo "`date` :: $1" >> /var/lib/beacons/awesome.log
echo $1
}
#### CHECK TO SEE IF IT HAS BEEN RUN BEFORE ####
FILE=/var/lib/beacons/file_fixes_and_new_services/add-custom-nix/completed
if [ -e $FILE ]; then
/run/current-system/sw/bin/echo "File Found :), No Need to Run ... Exiting"
exit 1
fi
#### CREATE INITIAL TAG ####
/run/current-system/sw/bin/mkdir -p /var/lib/beacons/file_fixes_and_new_services/add-custom-nix ; touch /var/lib/beacons/file_fixes_and_new_services/add-custom-nix/started
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Initial Tag"
exit 1
fi
#### MAIN SCRIPT ####
touch /etc/nixos/custom.nix
/run/current-system/sw/bin/cat > /etc/nixos/custom.nix <<- "EOF"
{config, pkgs, lib, ...}:
# Add custom NixOS modules here.
let
personalization = import ./personalization.nix;
in
{
}
EOF
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Run add-custom-nix"
exit 1
fi
#### CREATE COMPELETE TAG ####
/run/current-system/sw/bin/touch /var/lib/beacons/file_fixes_and_new_services/add-custom-nix/completed
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Completed Tag"
exit 1
fi
exit 0
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
function log_console () {
echo "`date` :: $1" >> /var/lib/beacons/awesome.log
echo $1
}
#### CHECK TO SEE IF IT HAS BEEN RUN BEFORE ####
FILE=/var/lib/beacons/file_fixes_and_new_services/add_external_backup_app/completed
if [ -e $FILE ]; then
/run/current-system/sw/bin/echo "File Found :), No Need to Run ... Exiting"
exit 1
fi
#### CREATE INITIAL TAG ####
/run/current-system/sw/bin/mkdir -p /var/lib/beacons/file_fixes_and_new_services/add_external_backup_app ; touch /var/lib/beacons/file_fixes_and_new_services/add_external_backup_app/started
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Initial Tag"
exit 1
fi
#### MAIN SCRIPT ####
cd /home/free/Downloads
/run/current-system/sw/bin/wget "https://git.sovransystems.com/Sovran_Systems/Software/raw/branch/main/Sovran_SystemsOS_External_Backup/sovran_systemsOS_external_backup_local_installer/sovran_systemsOS_external_backup_install.sh"
/run/current-system/sw/bin/bash "sovran_systemsOS_external_backup_install.sh"
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Run add_external_backup_app"
exit 1
fi
#### CREATE COMPELETE TAG ####
/run/current-system/sw/bin/touch /var/lib/beacons/file_fixes_and_new_services/add_external_backup_app/completed
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Completed Tag"
exit 1
fi
exit 0
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
function log_console () {
echo "`date` :: $1" >> /var/lib/beacons/awesome.log
echo $1
}
#### CHECK TO SEE IF IT HAS BEEN RUN BEFORE ####
FILE=/var/lib/beacons/file_fixes_and_new_services/element-calling_haven/completed
if [ -e $FILE ]; then
/run/current-system/sw/bin/echo "File Found :), No Need to Run ... Exiting"
exit 1
fi
#### CREATE INITIAL TAG ####
/run/current-system/sw/bin/mkdir -p /var/lib/beacons/file_fixes_and_new_services/element-calling_haven ; touch /var/lib/beacons/file_fixes_and_new_services/element-calling_haven/started
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Initial Tag"
exit 1
fi
#### MAIN SCRIPT ####
touch /var/lib/domains/haven
touch /var/lib/domains/element-calling
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Run element-calling_haven"
exit 1
fi
#### CREATE COMPELETE TAG ####
/run/current-system/sw/bin/touch /var/lib/beacons/file_fixes_and_new_services/element-calling_haven/completed
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Completed Tag"
exit 1
fi
exit 0
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
function log_console () {
echo "`date` :: $1" >> /var/lib/beacons/awesome.log
echo $1
}
#### CHECK TO SEE IF IT HAS BEEN RUN BEFORE ####
FILE=/var/lib/beacons/file_fixes_and_new_services/nextcloud_maintenance_window_fix/completed
if [ -e $FILE ]; then
/run/current-system/sw/bin/echo "File Found :), No Need to Run ... Exiting"
exit 1
fi
#### CREATE INITIAL TAG ####
/run/current-system/sw/bin/mkdir -p /var/lib/beacons/file_fixes_and_new_services/nextcloud_maintenance_window_fix ; touch /var/lib/beacons/file_fixes_and_new_services/nextcloud_maintenance_window_fix/started
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Initial Tag"
exit 1
fi
#### MAIN SCRIPT ####
/run/wrappers/bin/sudo -u caddy /run/current-system/sw/bin/php /var/lib/www/nextcloud/occ config:system:set maintenance_window_start --type=integer --value=1
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Run add-custom-nix"
exit 1
fi
#### CREATE COMPELETE TAG ####
/run/current-system/sw/bin/touch /var/lib/beacons/file_fixes_and_new_services/nextcloud_maintenance_window_fix/completed
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Completed Tag"
exit 1
fi
exit 0
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
function log_console () {
echo "`date` :: $1" >> /var/lib/beacons/awesome.log
echo $1
}
#### CHECK TO SEE IF IT HAS BEEN RUN BEFORE ####
FILE=/var/lib/beacons/file_fixes_and_new_services/sovran-pro-flake-update/completed
if [ -e $FILE ]; then
/run/current-system/sw/bin/echo "File Found :), No Need to Run ... Exiting"
exit 1
fi
#### CREATE INITIAL TAG ####
/run/current-system/sw/bin/mkdir -p /var/lib/beacons/file_fixes_and_new_services/sovran-pro-flake-update ; touch /var/lib/beacons/file_fixes_and_new_services/sovran-pro-flake-update/started
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Initial Tag"
exit 1
fi
#### MAIN SCRIPT ####
/run/current-system/sw/bin/rm /etc/nixos/flake.nix
/run/current-system/sw/bin/cat > /etc/nixos/flake.nix <<- "EOF"
{
description = "Sovran_SystemsOS for the Sovran Pro from Sovran Systems";
inputs = {
Sovran_Systems.url = "git+https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS";
};
outputs = { self, Sovran_Systems, ... }@inputs: {
nixosConfigurations."nixos" = Sovran_Systems.inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hardware-configuration.nix
Sovran_Systems.nixosModules.Sovran_SystemsOS
];
};
};
}
EOF
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Run sovran-pro-flake-update"
exit 1
fi
#### CREATE COMPELETE TAG ####
/run/current-system/sw/bin/touch /var/lib/beacons/file_fixes_and_new_services/sovran-pro-flake-update/completed
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Completed Tag"
exit 1
fi
exit 0
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
function log_console () {
echo "`date` :: $1" >> /var/lib/beacons/awesome.log
echo $1
}
#### CHECK TO SEE IF IT HAS BEEN RUN BEFORE ####
FILE=/var/lib/beacons/file_fixes_and_new_services/sovran-pro-flake-update2/completed
if [ -e $FILE ]; then
/run/current-system/sw/bin/echo "File Found :), No Need to Run ... Exiting"
exit 1
fi
#### CREATE INITIAL TAG ####
/run/current-system/sw/bin/mkdir -p /var/lib/beacons/file_fixes_and_new_services/sovran-pro-flake-update2 ; touch /var/lib/beacons/file_fixes_and_new_services/sovran-pro-flake-update2/started
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Initial Tag"
exit 1
fi
#### MAIN SCRIPT ####
/run/current-system/sw/bin/rm /etc/nixos/flake.nix
/run/current-system/sw/bin/cat > /etc/nixos/flake.nix <<- "EOF"
{
description = "Sovran_SystemsOS for the Sovran Pro from Sovran Systems";
inputs = {
Sovran_Systems.url = "git+https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS";
};
outputs = { self, Sovran_Systems, ... }@inputs: {
nixosConfigurations."nixos" = Sovran_Systems.inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./custom.nix
./hardware-configuration.nix
Sovran_Systems.nixosModules.Sovran_SystemsOS
];
};
};
}
EOF
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Run sovran-pro-flake-update2"
exit 1
fi
#### CREATE COMPELETE TAG ####
/run/current-system/sw/bin/touch /var/lib/beacons/file_fixes_and_new_services/sovran-pro-flake-update2/completed
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Completed Tag"
exit 1
fi
exit 0
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
#### CHECK TO SEE IF IT HAS BEEN RUN BEFORE ####
FILE=/var/lib/beacons/file_fixes_and_new_services/update-agenix/completed
if [ -e $FILE ]; then
/run/current-system/sw/bin/echo "File Found :), No Need to Run ... Exiting"
exit 1
fi
#### CREATE INITIAL TAG ####
/run/current-system/sw/bin/mkdir -p /var/lib/beacons/file_fixes_and_new_services/update-agenix ; touch /var/lib/beacons/file_fixes_and_new_services/update-agenix/started
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Initial Tag"
exit 1
fi
#### MAIN SCRIPT ####
/run/current-system/sw/bin/rm -rf /var/lib/agenix-secrets/nextclouddb.age
/run/current-system/sw/bin/rm -rf /var/lib/agenix-secrets/wordpressdb.age
/run/current-system/sw/bin/rm -rf /var/lib/agenix-secrets/turn.age
/run/current-system/sw/bin/rm -rf /var/lib/agenix-secrets/matrixdb.age
/run/current-system/sw/bin/rm -rf /var/lib/agenix-secrets/matrix_reg_secret.age
pushd /var/lib/agenix-secrets/
/run/current-system/sw/bin/echo -n $(/run/current-system/sw/bin/cat /var/lib/secrets/wordpressdb) | EDITOR='/run/current-system/sw/bin/cp /dev/stdin' /run/current-system/sw/bin/nix run github:ryantm/agenix -- -e wordpressdb.age -i /root/.ssh/agenix/agenix-secret-keys
/run/current-system/sw/bin/echo -n $(/run/current-system/sw/bin/cat /var/lib/secrets/nextclouddb) | EDITOR='/run/current-system/sw/bin/cp /dev/stdin' /run/current-system/sw/bin/nix run github:ryantm/agenix -- -e nextclouddb.age -i /root/.ssh/agenix/agenix-secret-keys
/run/current-system/sw/bin/echo -n $(/run/current-system/sw/bin/cat /var/lib/secrets/matrixdb) | EDITOR='/run/current-system/sw/bin/cp /dev/stdin' /run/current-system/sw/bin/nix run github:ryantm/agenix -- -e matrixdb.age -i /root/.ssh/agenix/agenix-secret-keys
/run/current-system/sw/bin/echo -n $(/run/current-system/sw/bin/cat /var/lib/secrets/turn) | EDITOR='/run/current-system/sw/bin/cp /dev/stdin' /run/current-system/sw/bin/nix run github:ryantm/agenix -- -e turn.age -i /root/.ssh/agenix/agenix-secret-keys
/run/current-system/sw/bin/echo -n $(/run/current-system/sw/bin/cat /var/lib/secrets/matrix_reg_secret) | EDITOR='/run/current-system/sw/bin/cp /dev/stdin' /run/current-system/sw/bin/nix run github:ryantm/agenix -- -e matrix_reg_secret.age -i /root/.ssh/agenix/agenix-secret-keys
popd
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Run update-agenix"
exit 1
fi
#### CREATE COMPELETE TAG ####
/run/current-system/sw/bin/touch /var/lib/beacons/file_fixes_and_new_services/update-agenix/completed
if [[ $? != 0 ]]; then
/run/current-system/sw/bin/echo "Could Not Create Completed Tag"
exit 1
fi
exit 0
Generated
+88 -14
View File
@@ -1,8 +1,29 @@
{
"nodes": {
"agenix": {
"inputs": {
"darwin": [],
"home-manager": "home-manager",
"nixpkgs": "nixpkgs",
"systems": "systems"
},
"locked": {
"lastModified": 1770165109,
"narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
"owner": "ryantm",
"repo": "agenix",
"rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
"type": "github"
},
"original": {
"owner": "ryantm",
"repo": "agenix",
"type": "github"
}
},
"bip110": {
"inputs": {
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1778967282,
@@ -20,7 +41,7 @@
},
"btc-clients": {
"inputs": {
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1779373552,
@@ -85,7 +106,7 @@
},
"flake-utils": {
"inputs": {
"systems": "systems"
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
@@ -101,11 +122,32 @@
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"agenix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1745494811,
"narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"nix-bitcoin": {
"inputs": {
"extra-container": "extra-container",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_3",
"nixpkgs": "nixpkgs_4",
"nixpkgs-25_05": "nixpkgs-25_05",
"nixpkgs-unstable": "nixpkgs-unstable"
},
@@ -126,16 +168,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1777728799,
"narHash": "sha256-z7jjYQqhkFKab92VQ3duB7QVO7f7Y62qTFrJYXO/lyo=",
"lastModified": 1754028485,
"narHash": "sha256-IiiXB3BDTi6UqzAZcf2S797hWEPCRZOwyNThJIYhUfk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4b2287113c2f9a2331c04899b2e2e5ab92dea9c5",
"rev": "59e69648d345d6e8fef86158c555730fa12af9de",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "master",
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
@@ -199,11 +241,27 @@
},
"original": {
"owner": "nixos",
"ref": "master",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1777728799,
"narHash": "sha256-z7jjYQqhkFKab92VQ3duB7QVO7f7Y62qTFrJYXO/lyo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4b2287113c2f9a2331c04899b2e2e5ab92dea9c5",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1778737229,
"narHash": "sha256-6xWoytx8jFW4PF1GjRm/i/53trbpKGfz6zjzQGBr4cI=",
@@ -219,7 +277,7 @@
"type": "github"
}
},
"nixpkgs_4": {
"nixpkgs_5": {
"locked": {
"lastModified": 1778869304,
"narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=",
@@ -235,7 +293,7 @@
"type": "github"
}
},
"nixpkgs_5": {
"nixpkgs_6": {
"locked": {
"lastModified": 1779259093,
"narHash": "sha256-7DKWmH23hL2eYdkxCKeqj2i+yljTKuU+3Nk1UPHOnxc=",
@@ -254,8 +312,8 @@
"nixvim": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs_5",
"systems": "systems_2"
"nixpkgs": "nixpkgs_6",
"systems": "systems_3"
},
"locked": {
"lastModified": 1779399125,
@@ -273,10 +331,11 @@
},
"root": {
"inputs": {
"agenix": "agenix",
"bip110": "bip110",
"btc-clients": "btc-clients",
"nix-bitcoin": "nix-bitcoin",
"nixpkgs": "nixpkgs_4",
"nixpkgs": "nixpkgs_5",
"nixpkgs-stable": "nixpkgs-stable",
"nixvim": "nixvim"
}
@@ -310,6 +369,21 @@
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
+29 -18
View File
@@ -2,60 +2,71 @@
description = "The Ultimate Sovran_SystemsOS Configuration from Sovran Systems";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nix-bitcoin.url = "github:fort-nix/nix-bitcoin/release";
agenix.url = "github:ryantm/agenix";
agenix.inputs.darwin.follows = "";
nixvim.url = "github:nix-community/nixvim";
btc-clients.url = "github:emmanuelrosa/btc-clients-nix";
nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-24.11";
bip110.url = "github:emmanuelrosa/bitcoin-knots-bip-110-nix";
};
outputs = { self, nixpkgs, nix-bitcoin, nixvim, btc-clients, nixpkgs-stable, bip110, ... }:
outputs = { self, nixpkgs, nix-bitcoin, nixvim, agenix, btc-clients, nixpkgs-stable, bip110, ... }:
let
overlay-stable = final: prev: {
stable = import nixpkgs-stable {
system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};
in
{
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
modules = [
{ nixpkgs.hostPlatform = "x86_64-linux"; }
self.nixosModules.Sovran_SystemsOS
./hardware-configuration.nix
./role-state.nix
./custom.nix
];
};
nixosConfigurations.sovran_systemsos-iso = nixpkgs.lib.nixosSystem {
};
in
{
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
modules = [
{ nixpkgs.hostPlatform = "x86_64-linux"; }
({ config, pkgs, ... }: { nixpkgs.overlays = [ overlay-stable ]; })
./iso/common.nix
nix-bitcoin.nixosModules.default
nixvim.nixosModules.nixvim
];
};
nixosModules.Sovran_SystemsOS = { pkgs, lib, config, ... }: {
imports = [
({ config, pkgs, ... }: {
nixpkgs.overlays = [ overlay-stable ];
})
./configuration.nix
nix-bitcoin.nixosModules.default
agenix.nixosModules.default
nixvim.nixosModules.nixvim
];
config = {
environment.systemPackages = with pkgs; [
btc-clients.packages.${pkgs.system}.bisq
btc-clients.packages.${pkgs.system}.bisq2
btc-clients.packages.${pkgs.system}.sparrow
];
sovran_systemsOS.packages.bip110 = bip110.packages.${pkgs.system}.bitcoind-knots-bip-110;
};
};
@@ -0,0 +1,472 @@
[com/ftpix/transparentbar]
dark-full-screen=false
[org/gnome/Connections]
first-run=false
[org/gnome/Console]
font-scale=1.6000000000000005
last-window-size=(1912, 1037)
[org/gnome/Geary]
migrated-config=true
window-height=516
window-width=954
[org/gnome/TextEditor]
last-save-directory='file:///home/free/Downloads'
[org/gnome/Totem]
active-plugins=['mpris', 'vimeo', 'screenshot', 'movie-properties', 'autoload-subtitles', 'screensaver', 'apple-trailers', 'save-file', 'rotation', 'open-directory', 'recent', 'variable-rate', 'skipto']
subtitle-encoding='UTF-8'
[org/gnome/baobab/ui]
is-maximized=false
window-size=(1912, 1037)
[org/gnome/calculator]
accuracy=9
angle-units='degrees'
base=10
button-mode='basic'
number-format='automatic'
show-thousands=false
show-zeroes=false
source-currency=''
source-units='degree'
target-currency=''
target-units='radian'
word-size=64
[org/gnome/calendar]
active-view='month'
window-maximized=false
window-size=(1912, 1037)
[org/gnome/control-center]
last-panel='background'
window-state=(1912, 1040, false)
[org/gnome/desktop/app-folders]
folder-children=['Utilities', 'YaST', 'd737daeb-6dbb-4a5d-9ec7-e674398539ce', '7d66e46a-a135-4e42-91bb-d438e499d251', '3fea025e-f5e4-4905-9912-e70e38cd0419', '83d8148a-1f0b-4f83-814a-11c33ab8debc', '68c075b1-a254-4b7c-ba63-c45f88bc2a58', '534e2716-83c7-4a2a-9678-8144999213ed', '4acaa2d8-d284-4efd-bba3-40f150f1ace5', '1e62b69b-d9bb-4e80-be8d-5e9b4d777fc8']
[org/gnome/desktop/app-folders/folders/1e62b69b-d9bb-4e80-be8d-5e9b4d777fc8]
apps=['math.desktop', 'writer.desktop', 'impress.desktop', 'draw.desktop', 'calc.desktop', 'base.desktop', 'startcenter.desktop']
name='Office'
[org/gnome/desktop/app-folders/folders/3fea025e-f5e4-4905-9912-e70e38cd0419]
apps=['cups.desktop', 'simple-scan.desktop']
name='Printing'
translate=false
[org/gnome/desktop/app-folders/folders/4acaa2d8-d284-4efd-bba3-40f150f1ace5]
apps=['org.gnome.DiskUtility.desktop', 'org.gnome.baobab.desktop', 'gparted.desktop', 'gnome-system-monitor.desktop']
name='Utilities'
[org/gnome/desktop/app-folders/folders/534e2716-83c7-4a2a-9678-8144999213ed]
apps=['org.gnome.Epiphany.desktop', 'librewolf.desktop', 'io.lbry.lbry-app.desktop', 'bitwarden.desktop', 'com.nextcloud.desktopclient.nextcloud.desktop', 'brave-browser.desktop', 'chromium-browser.desktop']
name='Internet'
[org/gnome/desktop/app-folders/folders/68c075b1-a254-4b7c-ba63-c45f88bc2a58]
apps=['org.gnome.Extensions.desktop', 'org.gnome.tweaks.desktop']
name='Customize Look'
translate=false
[org/gnome/desktop/app-folders/folders/7d66e46a-a135-4e42-91bb-d438e499d251]
apps=['org.gnome.Photos.desktop', 'org.gnome.Music.desktop', 'org.gnome.Totem.desktop', 'org.gnome.Cheese.desktop', 'org.gnome.Loupe.desktop', 'org.gnome.Snapshot.desktop']
name='Media'
translate=false
[org/gnome/desktop/app-folders/folders/83d8148a-1f0b-4f83-814a-11c33ab8debc]
apps=['org.gnome.Tour.desktop', 'yelp.desktop', 'nixos-manual.desktop']
name='Help'
translate=false
[org/gnome/desktop/app-folders/folders/Utilities]
apps=['gnome-abrt.desktop', 'gnome-system-log.desktop', 'nm-connection-editor.desktop', 'org.gnome.Connections.desktop', 'org.gnome.DejaDup.desktop', 'org.gnome.Dictionary.desktop', 'org.gnome.eog.desktop', 'org.gnome.Evince.desktop', 'org.gnome.FileRoller.desktop', 'org.gnome.fonts.desktop', 'org.gnome.seahorse.Application.desktop', 'org.gnome.Usage.desktop', 'vinagre.desktop', 'org.gnome.TextEditor.desktop', 'org.gnome.gedit.desktop', 'org.gnome.SystemMonitor.desktop']
categories=['X-GNOME-Utilities']
excluded-apps=['org.gnome.Console.desktop', 'org.gnome.tweaks.desktop', 'org.gnome.DiskUtility.desktop', 'org.gnome.baobab.desktop']
name='X-GNOME-Utilities.directory'
translate=true
[org/gnome/desktop/app-folders/folders/YaST]
categories=['X-SuSE-YaST']
name='suse-yast.directory'
translate=true
[org/gnome/desktop/app-folders/folders/d737daeb-6dbb-4a5d-9ec7-e674398539ce]
apps=['fish.desktop', 'org.gnome.Console.desktop', 'htop.desktop', 'ranger.desktop', 'xterm.desktop', 'org.gnome.Terminal.desktop']
name='Terminal Fun'
translate=false
[org/gnome/desktop/background]
color-shading-type='solid'
picture-options='zoom'
picture-uri='file:///run/current-system/sw/share/backgrounds/gnome/amber-l.jxl'
picture-uri-dark='file:///run/current-system/sw/share/backgrounds/gnome/amber-d.jxl'
primary-color='#ff7800'
secondary-color='#000000'
[org/gnome/desktop/calendar]
show-weekdate=false
[org/gnome/desktop/input-sources]
sources=[('xkb', 'us')]
xkb-options=['terminate:ctrl_alt_bksp']
[org/gnome/desktop/interface]
clock-format='12h'
clock-show-seconds=false
clock-show-weekday=false
color-scheme='prefer-dark'
enable-animations=true
font-antialiasing='rgba'
font-hinting='full'
gtk-theme='Adwaita-dark'
icon-theme='Papirus-Dark'
text-scaling-factor=1.0
[org/gnome/desktop/notifications]
application-children=['gnome-power-panel', 'org-gnome-nautilus', 'org-gnome-software', 'gnome-network-panel', 'sparrow', 'org-gnome-settings', 'org-gnome-console', 'gnome-printers-panel', 'org-gnome-epiphany', 'com-obsproject-studio', 'io-github-seadve-kooha', 'xdg-desktop-portal-gnome', 'org-gnome-baobab', 'org-gnome-geary', 'sparrow-desktop', 'impress', 'brave-browser', 'org-gnome-connections']
show-in-lock-screen=false
[org/gnome/desktop/notifications/application/brave-browser]
application-id='brave-browser.desktop'
[org/gnome/desktop/notifications/application/com-obsproject-studio]
application-id='com.obsproject.Studio.desktop'
[org/gnome/desktop/notifications/application/gnome-network-panel]
application-id='gnome-network-panel.desktop'
[org/gnome/desktop/notifications/application/gnome-power-panel]
application-id='gnome-power-panel.desktop'
[org/gnome/desktop/notifications/application/gnome-printers-panel]
application-id='gnome-printers-panel.desktop'
[org/gnome/desktop/notifications/application/impress]
application-id='impress.desktop'
[org/gnome/desktop/notifications/application/io-github-seadve-kooha]
application-id='io.github.seadve.Kooha.desktop'
[org/gnome/desktop/notifications/application/org-gnome-baobab]
application-id='org.gnome.baobab.desktop'
[org/gnome/desktop/notifications/application/org-gnome-connections]
application-id='org.gnome.Connections.desktop'
[org/gnome/desktop/notifications/application/org-gnome-console]
application-id='org.gnome.Console.desktop'
[org/gnome/desktop/notifications/application/org-gnome-epiphany]
application-id='org.gnome.Epiphany.desktop'
[org/gnome/desktop/notifications/application/org-gnome-geary]
application-id='org.gnome.Geary.desktop'
[org/gnome/desktop/notifications/application/org-gnome-nautilus]
application-id='org.gnome.Nautilus.desktop'
[org/gnome/desktop/notifications/application/org-gnome-settings]
application-id='org.gnome.Settings.desktop'
[org/gnome/desktop/notifications/application/org-gnome-software]
application-id='org.gnome.Software.desktop'
[org/gnome/desktop/notifications/application/sparrow-desktop]
application-id='sparrow-desktop.desktop'
[org/gnome/desktop/notifications/application/sparrow]
application-id='Sparrow.desktop'
[org/gnome/desktop/notifications/application/xdg-desktop-portal-gnome]
application-id='xdg-desktop-portal-gnome.desktop'
[org/gnome/desktop/peripherals/keyboard]
numlock-state=false
[org/gnome/desktop/peripherals/mouse]
natural-scroll=true
speed=-0.63779527559055116
[org/gnome/desktop/peripherals/touchpad]
two-finger-scrolling-enabled=true
[org/gnome/desktop/privacy]
old-files-age=uint32 30
recent-files-max-age=-1
[org/gnome/desktop/screensaver]
color-shading-type='solid'
lock-enabled=false
picture-options='zoom'
picture-uri='file:///run/current-system/sw/share/backgrounds/gnome/amber-l.jxl'
primary-color='#ff7800'
secondary-color='#000000'
[org/gnome/desktop/session]
idle-delay=uint32 900
[org/gnome/desktop/sound]
event-sounds=true
theme-name='__custom'
[org/gnome/desktop/wm/preferences]
button-layout='appmenu:minimize,maximize,close'
[org/gnome/epiphany]
ask-for-default=false
[org/gnome/epiphany/state]
is-maximized=false
window-size=(1912, 1037)
[org/gnome/evolution-data-server]
migrated=true
network-monitor-gio-name=''
[org/gnome/file-roller/dialogs/extract]
recreate-folders=true
skip-newer=false
[org/gnome/file-roller/listing]
list-mode='as-folder'
name-column-width=250
show-path=false
sort-method='name'
sort-type='ascending'
[org/gnome/file-roller/ui]
sidebar-width=200
window-height=993
window-width=954
[org/gnome/gnome-system-monitor]
current-tab='processes'
maximized=false
network-total-in-bits=false
show-dependencies=false
show-whose-processes='all'
window-height=1040
window-state=(1912, 1040, 26, 23)
window-width=1912
[org/gnome/gnome-system-monitor/disktreenew]
col-6-visible=true
col-6-width=0
[org/gnome/gnome-system-monitor/proctree]
columns-order=[0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
sort-col=8
sort-order=0
[org/gnome/maps]
last-viewed-location=[34.015438242460405, -118.32766985901287]
map-type='MapsStreetSource'
transportation-type='pedestrian'
window-maximized=false
window-size=[1912, 1037]
zoom-level=9
[org/gnome/mutter]
attach-modal-dialogs=true
dynamic-workspaces=true
edge-tiling=false
focus-change-on-pointer-rest=true
workspaces-only-on-primary=true
[org/gnome/nautilus/icon-view]
default-zoom-level='large'
[org/gnome/nautilus/preferences]
default-folder-viewer='icon-view'
fts-enabled=false
migrated-gtk-settings=true
search-filter-time-type='last_modified'
search-view='list-view'
[org/gnome/nautilus/window-state]
initial-size=(1912, 1040)
maximized=false
[org/gnome/nm-applet/eap/202ce1d2-7306-40ac-b3bb-5b092c0f9734]
ignore-ca-cert=false
ignore-phase2-ca-cert=false
[org/gnome/nm-applet/eap/2afa07ed-64ca-44a0-948e-d8f265fa52b0]
ignore-ca-cert=false
ignore-phase2-ca-cert=false
[org/gnome/nm-applet/eap/8da70f78-fe38-3e50-a305-8fa32b2af624]
ignore-ca-cert=false
ignore-phase2-ca-cert=false
[org/gnome/nm-applet/eap/a9f5fb1c-2546-4fb9-82d0-7792e8982565]
ignore-ca-cert=false
ignore-phase2-ca-cert=false
[org/gnome/nm-applet/eap/e5e312d5-e2db-3928-8c98-8ec8a7cf61f2]
ignore-ca-cert=false
ignore-phase2-ca-cert=false
[org/gnome/portal/filechooser/brave-browser]
last-folder-path='/home/free/Downloads'
[org/gnome/portal/filechooser/chromium-browser]
last-folder-path='/home/free/Downloads'
[org/gnome/settings-daemon/plugins/color]
night-light-enabled=true
night-light-schedule-automatic=false
night-light-schedule-from=18.0
night-light-temperature=uint32 1744
[org/gnome/settings-daemon/plugins/power]
power-button-action='nothing'
sleep-inactive-ac-type='nothing'
[org/gnome/shell]
app-picker-layout=[{'org.gnome.Weather.desktop': <{'position': <0>}>, 'org.gnome.clocks.desktop': <{'position': <1>}>, 'org.gnome.Maps.desktop': <{'position': <2>}>, 'org.gnome.Calculator.desktop': <{'position': <3>}>, '68c075b1-a254-4b7c-ba63-c45f88bc2a58': <{'position': <4>}>, '3fea025e-f5e4-4905-9912-e70e38cd0419': <{'position': <5>}>, '83d8148a-1f0b-4f83-814a-11c33ab8debc': <{'position': <6>}>, 'Utilities': <{'position': <7>}>, 'd737daeb-6dbb-4a5d-9ec7-e674398539ce': <{'position': <8>}>, '7d66e46a-a135-4e42-91bb-d438e499d251': <{'position': <9>}>, '534e2716-83c7-4a2a-9678-8144999213ed': <{'position': <10>}>, '4acaa2d8-d284-4efd-bba3-40f150f1ace5': <{'position': <11>}>, '1e62b69b-d9bb-4e80-be8d-5e9b4d777fc8': <{'position': <12>}>, 'Bisq-hidpi.desktop': <{'position': <13>}>, 'com.obsproject.Studio.desktop': <{'position': <14>}>, 'Sovran_SystemsOS_External_Backup.desktop': <{'position': <15>}>, 'firefox.desktop': <{'position': <16>}>}]
disable-user-extensions=false
disabled-extensions=['transparent-top-bar@zhanghai.me']
enabled-extensions=['appindicatorsupport@rgcjonas.gmail.com', 'dash-to-dock-cosmic-@halfmexicanhalfamazing@gmail.com', 'Vitals@CoreCoding.com', 'dash-to-dock@micxgx.gmail.com', 'transparent-top-bar@ftpix.com', 'just-perfection-desktop@just-perfection', 'pop-shell@system76.com', 'date-menu-formatter@marcinjakubowski.github.com', 'systemd-manager@hardpixel.eu', 'light-style@gnome-shell-extensions.gcampax.github.com']
favorite-apps=['firefox.desktop', 'org.gnome.Nautilus.desktop', 'Sovran_SystemsOS_Updater.desktop', 'org.gnome.Settings.desktop', 'org.gnome.Software.desktop', 'io.freetubeapp.FreeTube.desktop', 'org.onlyoffice.desktopeditors.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Contacts.desktop', 'org.gnome.Calendar.desktop', 'Bisq.desktop', 'sparrow-desktop.desktop']
last-selected-power-profile='performance'
welcome-dialog-last-shown-version='42.3.1'
[org/gnome/shell/extensions/dash-to-dock-pop]
apply-glossy-effect=false
background-color='rgb(0,0,0)'
background-opacity=0.25
border-radius=17
custom-background-color=true
custom-theme-shrink=false
dash-max-icon-size=64
dock-alignment='CENTRE'
dock-position='BOTTOM'
extend-height=false
floating-margin=0
force-straight-corner=false
height-fraction=0.90000000000000002
intellihide-mode='ALL_WINDOWS'
preferred-monitor=-2
preferred-monitor-by-connector='HDMI-1'
preview-size-scale=0.059999999999999998
running-indicator-style='DASHES'
show-apps-at-top=false
show-mounts=false
show-show-apps-button=true
show-trash=false
transparency-mode='FIXED'
unity-backlit-items=false
[org/gnome/shell/extensions/dash-to-dock]
apply-custom-theme=false
background-color='rgb(0,0,0)'
background-opacity=0.17000000000000001
custom-background-color=true
dash-max-icon-size=57
dock-position='BOTTOM'
extend-height=false
height-fraction=0.89000000000000001
icon-size-fixed=false
intellihide-mode='ALL_WINDOWS'
preferred-monitor=-2
preferred-monitor-by-connector='HDMI-2'
preview-size-scale=0.22
running-indicator-style='DASHES'
show-mounts=false
show-mounts-only-mounted=false
show-trash=false
transparency-mode='FIXED'
[org/gnome/shell/extensions/date-menu-formatter]
font-size=14
pattern='EEEE MMMM d h: mm a'
text-align='center'
[org/gnome/shell/extensions/just-perfection]
accessibility-menu=false
[org/gnome/shell/extensions/pop-shell]
active-hint-border-radius=uint32 3
gap-inner=uint32 1
gap-outer=uint32 1
tile-by-default=true
[org/gnome/shell/extensions/systemd-manager]
command-method='systemctl'
systemd=['{"name":"Bitcoind","service":"bitcoind.service","type":"system"}', '{"name":"Electrs","service":"electrs.service","type":"system"}', '{"name":"BTCPayserver","service":"btcpayserver.service","type":"system"}', '{"name":"Nbxplorer","service":"nbxplorer.service","type":"system"}', '{"name":"Caddy","service":"caddy.service","type":"system"}', '{"name":"Phpfpm-Mypool","service":"phpfpm-mypool.service","type":"system"}', '{"name":"Mysql","service":"mysql.service","type":"system"}', '{"name":"Postgresql","service":"postgresql.service","type":"system"}', '{"name":"Matrix-Synapse","service":"matrix-synapse.service","type":"system"}', '{"name":"Coturn","service":"coturn.service","type":"system"}', '{"name":"Tor","service":"tor.service","type":"system"}', '{"name":"VaultWarden","service":"vaultwarden.service","type":"system"}', '{"name":"LND","service":"lnd.service","type":"system"}', '{"name":"LND Loop","service":"lightning-loop.service","type":"system"}', '{"name":"Ride The Lightning","service":"rtl.service","type":"system"}']
[org/gnome/shell/extensions/vitals]
fixed-widths=false
hot-sensors=['_memory_usage_', '__network-tx_max__', '_processor_usage_', '_storage_free_', '_temperature_processor_0_']
show-fan=false
show-storage=true
show-voltage=false
[org/gnome/shell/weather]
automatic-location=true
locations=@av []
[org/gnome/shell/world-clocks]
locations=@av []
[org/gnome/software]
check-timestamp=int64 1715525466
first-run=false
flatpak-purge-timestamp=int64 1715478601
online-updates-timestamp=int64 1675355639
update-notification-timestamp=int64 1666382024
[org/gnome/terminal/legacy/profiles:/:b1dcc9dd-5262-4d8d-a863-c897e6d979b9]
font='Monospace 14'
use-system-font=false
[org/gnome/tweaks]
show-extensions-notice=false
[org/gtk/gtk4/settings/color-chooser]
selected-color=(true, 0.0, 0.0, 0.0, 1.0)
[org/gtk/gtk4/settings/file-chooser]
date-format='regular'
location-mode='path-bar'
show-hidden=false
show-size-column=true
show-type-column=true
sidebar-width=140
sort-column='name'
sort-directories-first=false
sort-order='ascending'
type-format='category'
view-type='list'
window-size=(1912, 1040)
[org/gtk/settings/file-chooser]
clock-format='12h'
date-format='regular'
location-mode='path-bar'
show-hidden=true
show-size-column=true
show-type-column=true
sidebar-width=165
sort-column='modified'
sort-directories-first=false
sort-order='descending'
type-format='category'
window-position=(26, 23)
window-size=(1401, 998)
[system/proxy]
ignore-hosts=@as []
mode='none'
[system/proxy/http]
port=0
[system/proxy/socks]
host='127.0.0.1'
port=9050
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

+30
View File
@@ -0,0 +1,30 @@
{
description = "Sovran_SystemsOS for the Sovran Pro from Sovran Systems";
inputs = {
Sovran_Systems.url = "git+https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS";
};
outputs = { self, Sovran_Systems, ... }@inputs: {
nixosConfigurations."nixos" = Sovran_Systems.inputs.nixpkgs.lib.nixosSystem {
modules = [
{ nixpkgs.hostPlatform = "x86_64-linux"; }
./hardware-configuration.nix
./custom.nix
Sovran_Systems.nixosModules.Sovran_SystemsOS
];
};
};
}
+30
View File
@@ -0,0 +1,30 @@
{
description = "Sovran_SystemsOS for the Sovran Pro from Sovran Systems";
inputs = {
Sovran_Systems.url = "git+https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS";
};
outputs = { self, Sovran_Systems, ... }@inputs: {
nixosConfigurations."nixos" = Sovran_Systems.inputs.nixpkgs.lib.nixosSystem {
modules = [
{ nixpkgs.hostPlatform = "x86_64-linux"; }
./hardware-configuration.nix
./custom.nix
Sovran_Systems.nixosModules.Sovran_SystemsOS
];
};
};
}
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# Begin: curl https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/psp.sh -o psp.sh
GREEN="\e[32m"
LIGHTBLUE="\e[94m"
ENDCOLOR="\e[0m"
lsblk
echo -e "${GREEN}What block for file-tree-root of drive (usually nvme0n1)?${ENDCOLOR}";read commitroot
parted /dev/"$commitroot" -- mklabel gpt
parted /dev/"$commitroot" -- mkpart primary 512MB -16GB
parted /dev/"$commitroot" -- mkpart swap linux-swap -16GB 100%
parted /dev/"$commitroot" -- mkpart ESP fat32 1MB 512MB
parted /dev/"$commitroot" -- set 3 esp on
lsblk
echo -e "${GREEN}What partition for Boot-Partition (usually nvme0n1p1)?${ENDCOLOR}";read commitbootpartition
echo -e "${GREEN}What partition for Main-Partition (usually nvme0n1p2)?${ENDCOLOR}";read commitmainpartition
echo -e "${GREEN}What partition for Swap-Partition (usually nvme0n1p3)?${ENDCOLOR}";read commitswappartition
mkfs.ext4 -L nixos /dev/"$commitmainpartition"
mkswap -L swap /dev/"$commitswappartition"
mkfs.fat -F 32 -n boot /dev/"$commitbootpartition"
mount /dev/disk/by-label/nixos /mnt
mkdir -p /mnt/boot/efi
mount /dev/disk/by-label/boot /mnt/boot/efi
nixos-generate-config --root /mnt
rm /mnt/etc/nixos/configuration.nix
cat <<EOT >> /mnt/etc/nixos/configuration.nix
{ config, pkgs, ... }: {
imports = [
./hardware-configuration.nix
];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.efi.efiSysMountPoint = "/boot/efi";
nix.settings.experimental-features = [ "nix-command" "flakes" ];
users.users = {
free = {
isNormalUser = true;
description = "free";
extraGroups = [ "networkmanager" ];
};
};
environment.systemPackages = with pkgs; [
wget
git
ranger
fish
pwgen
openssl
];
services.openssh = {
enable = true;
permitRootLogin = "yes";
};
}
EOT
nixos-install
reboot
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# Begin: curl https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/psp_physical_ram.sh -o psp_physical_ram.sh
GREEN="\e[32m"
LIGHTBLUE="\e[94m"
ENDCOLOR="\e[0m"
lsblk
echo -e "${GREEN}What block for file-tree-root of drive (usually nvme0n1)?${ENDCOLOR}";read commitroot
parted /dev/"$commitroot" -- mklabel gpt
parted /dev/"$commitroot" -- mkpart ESP fat32 1MB 512MB
parted /dev/"$commitroot" -- set 1 esp on
parted /dev/"$commitroot" -- mkpart primary ext4 512MB 100%
lsblk
echo -e "${GREEN}What partition for Boot-Partition (usually nvme0n1p1)?${ENDCOLOR}";read commitbootpartition
echo -e "${GREEN}What partition for Primary-Partition (usually nvme0n1p2)?${ENDCOLOR}";read commitprimarypartition
mkfs.ext4 -L nixos /dev/"$commitprimarypartition"
mkfs.fat -F 32 -n boot /dev/"$commitbootpartition"
mount /dev/disk/by-label/nixos /mnt
mkdir -p /mnt/boot/efi
mount /dev/disk/by-label/boot /mnt/boot/efi
### Disk Step-up Finished
### Adding Configuration.nix
nixos-generate-config --root /mnt
rm /mnt/etc/nixos/configuration.nix
cat <<EOT >> /mnt/etc/nixos/configuration.nix
{ config, pkgs, ... }: {
imports = [
./hardware-configuration.nix
];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.efi.efiSysMountPoint = "/boot/efi";
nix.settings.experimental-features = [ "nix-command" "flakes" ];
users.users = {
free = {
isNormalUser = true;
description = "free";
extraGroups = [ "networkmanager" ];
};
};
environment.systemPackages = with pkgs; [
wget
git
ranger
fish
pwgen
openssl
];
services.openssh = {
enable = true;
permitRootLogin = "yes";
};
}
EOT
nixos-install
reboot
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
GREEN="\e[32m"
LIGHTBLUE="\e[94m"
ENDCOLOR="\e[0m"
lsblk
echo -e "${GREEN}What block for New Sovran Pro Second drive?${ENDCOLOR}";read commitroot
parted /dev/"$commitroot" -- mklabel gpt
parted /dev/"$commitroot" -- mkpart primary 0% 100%
lsblk
echo -e "${GREEN}What partition with New Sovran Pro Second Drive?${ENDCOLOR}";read commitsecond
mkfs.ext4 -L "BTCEcoandBackup" /dev/"$commitsecond"
sudo mkdir -p /mnt
mount /dev/"$commitsecond" /mnt
sudo mkdir -p /mnt/BTCEcoandBackup/Bitcoin_Node
sudo mkdir -p /mnt/BTCEcoandBackup/Electrs_Data
sudo mkdir -p /mnt/BTCEcoandBackup/NixOS_Snapshot_Backup
sudo mkdir -p /mnt/BTCEcoandBackup/clightning_db_backup
sudo systemctl stop bitcoind electrs nbxplorer btcpayserver lnd rtl lightning-loop clightning
rsync -ar --info=progress2 --info=name0 /run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node/ /mnt/BTCEcoandBackup/Bitcoin_Node/
rsync -ar --info=progress2 --info=name0 /run/media/Second_Drive/BTCEcoandBackup/Electrs_Data/ /mnt/BTCEcoandBackup/Electrs_Data/
sudo systemctl start bitcoind electrs nbxplorer btcpayserver lnd rtl lightning-loop clightning
sudo chown bitcoin:bitcoin /mnt/BTCEcoandBackup/Bitcoin_Node -R
sudo chown electrs:electrs /mnt/BTCEcoandBackup/Electrs_Data -R
sudo chmod 770 /mnt/BTCEcoandBackup/Bitcoin_Node -R
sudo chmod 770 /mnt/BTCEcoandBackup/Electrs_Data -R
sudo umount /dev/"$commitsecond"
echo -e "All Finished!"
+406
View File
@@ -0,0 +1,406 @@
#!/usr/bin/env bash
# wget https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/sp.sh
GREEN="\e[32m"
LIGHTBLUE="\e[94m"
#
pushd /etc/nixos/
wget https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/flake.nix
chown root:root /etc/nixos/ -R
chmod 770 /etc/nixos/ -R
popd
#
mkdir /var/lib/domains
touch /var/lib/domains/btcpayserver
touch /var/lib/domains/matrix
touch /var/lib/domains/nextcloud
touch /var/lib/domains/sslemail
touch /var/lib/domains/vaultwarden
touch /var/lib/domains/wordpress
#
echo -e "${GREEN}What is your New Matrix (Element Chat) domain name?${ENDCOLOR}"
read
echo -n $REPLY > /var/lib/domains/matrix
echo -e "${GREEN}What is your New Wordpress domain name?${ENDCOLOR}"
read
echo -n $REPLY > /var/lib/domains/wordpress
echo -e "${GREEN}What is your New Nextcloud domain name?${ENDCOLOR}"
read
echo -n $REPLY > /var/lib/domains/nextcloud
echo -e "${GREEN}What is your New BTCPayserver domain name?${ENDCOLOR}"
read
echo -n $REPLY > /var/lib/domains/btcpayserver
echo -e "${GREEN}What is your New Vaultwarden domain name?${ENDCOLOR}"
read
echo -n $REPLY > /var/lib/domains/vaultwarden
echo -e "${GREEN}What is the email you would like to use to manage the SSL certificates for your domains?${ENDCOLOR}"
read
echo -n $REPLY > /var/lib/domains/sslemail
#
mkdir /var/lib/nextcloudaddition
cat > /var/lib/nextcloudaddition/nextcloudaddition <<- "EOF"
'trusted_proxies' =>
array (
0 => '127.0.0.1',
),
'default_locale' => 'en_US',
'default_phone_region' => 'US',
'memcache.local' =>'\OC\Memcache\APCu' ,
EOF
#
mkdir /var/lib/njalla/
cat > /var/lib/njalla/njalla.sh <<- "EOF"
#!/usr/bin/env bash
IP=$(dig @resolver4.opendns.com myip.opendns.com +short -4)
## Manually Add DDNS Script From Njalla User Account AFTER Install
curl "https://...${IP}"
EOF
#
mkdir /var/lib/external_ip
cat > /var/lib/external_ip/external_ip.sh <<- "EOF"
#!/usr/bin/env bash
IP=$(dig @resolver4.opendns.com myip.opendns.com +short -4)
echo "${IP}" > /var/lib/secrets/external_ip
EOF
#
mkdir /var/lib/internal_ip
cat > /var/lib/internal_ip/internal_ip.sh <<- "EOF"
#!/usr/bin/env bash
sudo echo -n $(ip route get 1.2.3.4 | awk '{print $7}') > /var/lib/secrets/internal_ip
exit 0
EOF
#
touch /etc/nixos/custom.nix
cat > /etc/nixos/custom.nix <<- "EOF"
{config, pkgs, lib, ...}:
let
personalization = import ./personalization.nix;
in
{
}
EOF
#
mkdir /var/lib/agenix-secrets/
cat > /var/lib/agenix-secrets/secrets.nix <<- "EOF"
let
root = "placeholder" ;
in
{
"wordpressdb.age".publicKeys = [ root ];
"matrixdb.age".publicKeys = [ root ];
"nextclouddb.age".publicKeys = [ root ];
"turn.age".publicKeys = [ root ];
"matrix_reg_secret.age".publicKeys = [ root ];
}
EOF
#
mkdir /var/lib/secrets
mkdir /var/lib/secrets/vaultwarden
touch /var/lib/secrets/nextclouddb
touch /var/lib/secrets/wordpressdb
touch /var/lib/secrets/matrixdb
touch /var/lib/secrets/turn
touch /var/lib/secrets/matrix_reg_secret
touch /var/lib/secrets/main
touch /var/lib/secrets/vaultwarden/vaultwarden.env
touch /var/lib/secrets/external_ip
touch /var/lib/secrets/internal_ip
echo -n $(pwgen -s 17 -1) > /var/lib/secrets/nextclouddb
echo -n $(pwgen -s 17 -1) > /var/lib/secrets/wordpressdb
echo -n $(pwgen -s 17 -1) > /var/lib/secrets/matrixdb
echo -n $(pwgen -s 17 -1) > /var/lib/secrets/turn
echo -n $(pwgen -s 17 -1) > /var/lib/secrets/matrix_reg_secret
echo -n $(pwgen -s 17 -1) > /var/lib/secrets/main
echo -n ADMIN_TOKEN=$(openssl rand -base64 48
) > /var/lib/secrets/vaultwarden/vaultwarden.env
#
mkdir -p /root/.ssh/agenix
ssh-keygen -q -N "" -t ed25519 -f /root/.ssh/agenix/agenix-secret-keys
sed -i -e "0,/root.*/{s::root = $(cat /root/.ssh/agenix/agenix-secret-keys.pub):};s:root@nixos::" /var/lib/agenix-secrets/secrets.nix
sed -i 's:\(root =[[:blank:]]*\)\(.*\):\1"\2";:' /var/lib/agenix-secrets/secrets.nix
#
pushd /var/lib/agenix-secrets
echo -n $(cat /var/lib/secrets/wordpressdb) | EDITOR='cp /dev/stdin' nix run github:ryantm/agenix -- -e wordpressdb.age -i /root/.ssh/agenix/agenix-secret-keys
echo -n $(cat /var/lib/secrets/nextclouddb) | EDITOR='cp /dev/stdin' nix run github:ryantm/agenix -- -e nextclouddb.age -i /root/.ssh/agenix/agenix-secret-keys
echo -n $(cat /var/lib/secrets/matrixdb) | EDITOR='cp /dev/stdin' nix run github:ryantm/agenix -- -e matrixdb.age -i /root/.ssh/agenix/agenix-secret-keys
echo -n $(cat /var/lib/secrets/turn) | EDITOR='cp /dev/stdin' nix run github:ryantm/agenix -- -e turn.age -i /root/.ssh/agenix/agenix-secret-keys
echo -n $(cat /var/lib/secrets/matrix_reg_secret) | EDITOR='cp /dev/stdin' nix run github:ryantm/agenix -- -e matrix_reg_secret.age -i /root/.ssh/agenix/agenix-secret-keys
popd
#
pushd /etc/nixos
nix flake update
nixos-rebuild switch --impure
popd
#
chown root:root /var/lib/secrets/main -R
chown root:root /var/lib/secrets/external_ip -R
chown root:root /var/lib/secrets/internal_ip -R
chown matrix-synapse:matrix-synapse /var/lib/secrets/matrix_reg_secret -R
chown matrix-synapse:matrix-synapse /var/lib/secrets/matrixdb -R
chown postgres:postgres /var/lib/secrets/nextclouddb -R
chown turnserver:turnserver /var/lib/secrets/turn -R
chown mysql:mysql /var/lib/secrets/wordpressdb -R
chown vaultwarden:vaultwarden /var/lib/secrets/vaultwarden -R
chmod 770 /var/lib/secrets/ -R
#
chown caddy:php /var/lib/domains -R
chmod 770 /var/lib/domains -R
#
set -x
wget -P /var/lib/www/downloadwp https://wordpress.org/latest.zip
wget -P /var/lib/www/downloadnc https://download.nextcloud.com/server/releases/latest.zip
unzip /var/lib/www/downloadwp/latest.zip -d /var/lib/www/
unzip /var/lib/www/downloadnc/latest.zip -d /var/lib/www/
rm -rf /var/lib/www/downloadwp
rm -rf /var/lib/www/downloadnc
chown caddy:php /var/lib/www -R
chmod 770 /var/lib/www -R
#
mkdir /var/lib/nextcloud
chown caddy:php /var/lib/nextcloud -R
chmod 770 /var/lib/nextcloud -R
#
mkdir /var/lib/coturn
chown turnserver:turnserver /var/lib/coturn -R
chmod 770 /var/lib/coturn -R
#
rm -rf /root/sp.sh
#
chown bitcoin:bitcoin /run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node -R
chmod 770 /run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node -R
chown electrs:electrs /run/media/Second_Drive/BTCEcoandBackup/Electrs_Data -R
chmod 770 /run/media/Second_Drive/BTCEcoandBackup/Electrs_Data -R
#
mkdir -p /home/free/Downloads
pushd /home/free/Downloads
wget https://git.sovransystems.com/Sovran_Systems/Software/raw/branch/main/Sovran_SystemsOS_Resetter/sovran_systemsOS_resetter_local_installer/sovran_systemsOS_resetter_install.sh
bash sovran_systemsOS_resetter_install.sh
popd
#
pushd /home/free/Downloads
wget https://git.sovransystems.com/Sovran_Systems/Software/raw/branch/main/Sovran_SystemsOS_Updater/sovran_systemsOS_updater_local_installer/sovran_systemsOS_updater_install.sh
bash sovran_systemsOS_updater_install.sh
popd
#
mkdir -p /home/free/Pictures
pushd /home/free/Pictures
wget https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/Wallpaper_Dark_Wide.png
popd
chown free:users /home/free -R
chmod 700 /home/free -R
#
pushd /home/free/Downloads
sudo -u free wget https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/for_new_sovran_pros/Sovran_SystemsOS-Desktop
popd
#
wp=$(cat /var/lib/secrets/wordpressdb)
sudo mysql -u root -e "SET PASSWORD FOR wpusr@localhost = PASSWORD('${wp}')";
#
mkdir /root/.ssh
mkdir -p /home/free/.ssh
chown free:users /home/free/.ssh -R
touch /root/.ssh/authorized_keys
sudo -u free ssh-keygen -q -N "gosovransystems" -t ed25519 -f /home/free/.ssh/factory_login
chmod 700 /home/free/.ssh -R
echo "$(cat /home/free/.ssh/factory_login.pub)" >> /root/.ssh/authorized_keys
#
sudo matrix-synapse-register_new_matrix_user -u admin -p a -a
sudo echo "no" | matrix-synapse-register_new_matrix_user -u test -p a
#
# This key is removed before shipping as it allows Sovran Systems to access the machine via root remotely.
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCQa3DEhx9RUtV0WopfFuL3cjQt2fBzp5wOg/hkj0FXyZXpp+F47Td1B9mKMNvucINaMQB6T0mW6c70fyT92gZO2OqCff6aeWovtTd9ynRgtJbny/qvVSShDbJcR7nSMeVPoDRaYs18fuA50guYnfoYAkaXyXPmVQ0uK84HwIB5j8gq6GMji7vv+TTNhDP8qOceUzt1DYPo9Z2JSnkFey+Z/fmxWJGsu+MSrA0/PPENEmf6L0ZSgxnu3gHEtdyX2hrFzjE16y3G0wSQzbWJb8MJO0KRSMcyvz6AzOSW4RYdXR1c+4JiciKRdnIAYYHfg7tnZT9wC9AzHjdEbmmrlF05mtjXKnxbPgGY0tlRSYo7B5E0k2zfi30MkIJ6kIE9TMM2z/+1KstrQN4OKBTGomBTYQaRQCT6dGpRTR+b8lOvUcnCSuat1sUC2M2VGFcBbDbKD0FyXy/vOk1pgA4I7GoESWQClnl+ntRg8HrW4oVTX2KpqR2CXjlF956HJGqHW6k= free@nixos" >> /root/.ssh/authorized_keys
#
pushd /etc/nixos
nix flake update
nixos-rebuild switch --impure
popd
#
echo "root:$(cat /var/lib/secrets/main)" | chpasswd -c SHA512
echo "free:a" | chpasswd -c SHA512
#
chown free:users /home/free -R
chmod 700 /home/free -R
#
echo -e "${GREEN}All Finished! Please Reboot then Enjoy your New Sovran Pro!"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

-185
View File
@@ -1,185 +0,0 @@
{ config, pkgs, lib, modulesPath, ... }:
let
sovranSource = builtins.path { path = ../.; name = "sovran-systemsos"; };
pythonEnv = pkgs.python3.withPackages (ps: [ ps.pygobject3 ps.pycairo ]);
installerPy = pkgs.writeShellScriptBin "sovran-install" ''
export GI_TYPELIB_PATH=${pkgs.gtk4}/lib/girepository-1.0:${pkgs.libadwaita}/lib/girepository-1.0:${pkgs.glib}/lib/girepository-1.0:${pkgs.pango.out}/lib/girepository-1.0:${pkgs.gdk-pixbuf}/lib/girepository-1.0:${pkgs.graphene}/lib/girepository-1.0:${pkgs.cairo}/lib/girepository-1.0:${pkgs.harfbuzz}/lib/girepository-1.0:${pkgs.gobject-introspection}/lib/girepository-1.0
export LD_LIBRARY_PATH=${pkgs.gtk4}/lib:${pkgs.libadwaita}/lib:${pkgs.glib}/lib:${pkgs.pango.out}/lib:${pkgs.gdk-pixbuf}/lib:${pkgs.graphene}/lib:${pkgs.cairo}/lib:${pkgs.harfbuzz}/lib
export GDK_PIXBUF_MODULE_FILE="${pkgs.gdk-pixbuf}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
export XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk4}/share:${pkgs.libadwaita}/share:${pkgs.adwaita-icon-theme}/share:${pkgs.hicolor-icon-theme}/share:$XDG_DATA_DIRS"
exec ${pythonEnv}/bin/python3 /etc/sovran/installer.py
'';
in
{
imports = [
"${modulesPath}/installer/cd-dvd/installation-cd-graphical-gnome.nix"
./branding.nix
];
image.baseName = lib.mkForce "Sovran_SystemsOS";
isoImage.splashImage = ./assets/splash-logo.png;
services.gnome.gnome-initial-setup.enable = false;
environment.gnome.excludePackages = with pkgs; [ gnome-tour gnome-user-docs ];
security.sudo.wheelNeedsPassword = false;
users.users.free = {
isNormalUser = true;
description = "free";
extraGroups = [ "networkmanager" "wheel" ];
initialPassword = "free";
};
services.displayManager.autoLogin.enable = true;
services.displayManager.autoLogin.user = lib.mkForce "free";
nix-bitcoin.generateSecrets = lib.mkDefault true;
nix.settings.experimental-features = [ "nix-command" "flakes" ];
environment.systemPackages = with pkgs; [
installerPy
pythonEnv
gtk4
libadwaita
gobject-introspection
glib
pango
gdk-pixbuf
graphene
cairo
harfbuzz
gsettings-desktop-schemas
adwaita-icon-theme
util-linux
parted
dosfstools
e2fsprogs
gptfdisk
nixos-install-tools
git
curl
openssh
tailscale
jq
xxd
];
# Remote install support — SSH on the live ISO
services.openssh = {
enable = true;
listenAddresses = [{ addr = "0.0.0.0"; port = 22; }];
settings = {
PasswordAuthentication = true;
PermitRootLogin = "yes";
};
};
users.users.root.initialPassword = lib.mkForce "sovran-remote";
users.users.root.initialHashedPassword = lib.mkForce null;
# mDNS so the machine is discoverable as sovran-installer.local
services.avahi = {
enable = true;
hostName = "sovran-installer";
nssmdns4 = true;
publish = { enable = true; addresses = true; };
};
environment.etc."sovran/logo.png".source = ./assets/splash-logo.png;
environment.etc."sovran/flake".source = sovranSource;
environment.etc."sovran/installer.py".source = ./installer.py;
# These files are gitignored — set at build time by placing them in iso/secrets/
environment.etc."sovran/enroll-token" = lib.mkIf (builtins.pathExists ./secrets/enroll-token) {
text = builtins.readFile ./secrets/enroll-token;
mode = "0600";
};
environment.etc."sovran/provisioner-url" = lib.mkIf (builtins.pathExists ./secrets/provisioner-url) {
text = builtins.readFile ./secrets/provisioner-url;
mode = "0644";
};
# Tailscale client for mesh VPN
services.tailscale.enable = true;
# Auto-provision service — registers with provisioning server and joins Tailnet
systemd.services.sovran-auto-provision = {
description = "Auto-register with Sovran provisioning server and join Tailnet";
after = [ "network-online.target" "tailscaled.service" ];
wants = [ "network-online.target" "tailscaled.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.tailscale pkgs.curl pkgs.jq pkgs.coreutils pkgs.iproute2 pkgs.xxd ];
script = ''
TOKEN_FILE="/etc/sovran/enroll-token"
URL_FILE="/etc/sovran/provisioner-url"
[ -f "$TOKEN_FILE" ] || { echo "No enroll token found, skipping auto-provision"; exit 0; }
[ -f "$URL_FILE" ] || { echo "No provisioner URL found, skipping auto-provision"; exit 0; }
TOKEN=$(cat "$TOKEN_FILE")
PROV_URL=$(cat "$URL_FILE")
[ -n "$TOKEN" ] || exit 0
[ -n "$PROV_URL" ] || exit 0
# Wait for network + tailscaled
sleep 10
# Collect machine info
HOSTNAME="sovran-deploy-$(head -c 8 /dev/urandom | xxd -p)"
MAC=$(ip link show | grep ether | head -1 | awk '{print $2}' || echo "unknown")
echo "Registering with provisioning server at $PROV_URL..."
# Retry up to 6 times (covers slow DHCP)
RESPONSE=""
for i in $(seq 1 6); do
RESPONSE=$(curl -sf --max-time 15 -X POST \
"$PROV_URL/register" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"hostname\": \"$HOSTNAME\", \"mac\": \"$MAC\"}" 2>/dev/null) && break
echo "Attempt $i failed, retrying in 10s..."
sleep 10
done
if [ -z "$RESPONSE" ]; then
echo "ERROR: Failed to register with provisioning server after 6 attempts"
exit 1
fi
HS_KEY=$(echo "$RESPONSE" | jq -r '.headscale_key')
LOGIN_SERVER=$(echo "$RESPONSE" | jq -r '.login_server')
if [ -z "$HS_KEY" ] || [ "$HS_KEY" = "null" ]; then
echo "ERROR: No Headscale key in response: $RESPONSE"
exit 1
fi
echo "Joining Tailnet via $LOGIN_SERVER as $HOSTNAME..."
tailscale up \
--login-server="$LOGIN_SERVER" \
--authkey="$HS_KEY" \
--hostname="$HOSTNAME"
TAILSCALE_IP=$(tailscale ip -4)
echo "Successfully joined Tailnet as $HOSTNAME ($TAILSCALE_IP)"
'';
};
environment.etc."xdg/autostart/sovran-installer.desktop".text = ''
[Desktop Entry]
Type=Application
Name=Sovran Guided Installer
Exec=${installerPy}/bin/sovran-install
Terminal=false
X-GNOME-Autostart-enabled=true
'';
}
-5
View File
@@ -1,5 +0,0 @@
{ config, pkgs, lib, ... }:
{
imports = [ ./common.nix ];
}
-1206
View File
File diff suppressed because it is too large Load Diff
View File
-5
View File
@@ -1,5 +0,0 @@
{ config, pkgs, lib, ... }:
{
imports = [ ./common.nix ];
}
-278
View File
@@ -1,278 +0,0 @@
#!/usr/bin/env bash
# sovran-install-headless.sh — Non-interactive remote installer for Sovran_SystemsOS
usage() {
cat <<'USAGE'
Usage: sovran-install-headless.sh [OPTIONS]
Options:
--disk /dev/sda Target OS disk (required)
--data-disk /dev/sdb Data disk for Bitcoin (optional)
--role server|desktop|node Installation role (default: server)
--deploy-key "ssh-ed25519 AAAA..." SSH pubkey for remote access after install
--headscale-server URL Headscale login server for post-install Tailnet
--headscale-key KEY Headscale pre-auth key for the installed OS
USAGE
}
set -euo pipefail
# ── Defaults ──────────────────────────────────────────────────────────────────
DISK=""
DATA_DISK=""
ROLE="server"
DEPLOY_KEY=""
HEADSCALE_SERVER=""
HEADSCALE_KEY=""
DATA_DISK_HAS_TIMECHAIN=false
FLAKE="/etc/sovran/flake"
LOG="/tmp/sovran-headless-install.log"
BYTES_256GB=$((256 * 1024 * 1024 * 1024))
BYTES_2TB=$((2 * 1000 * 1000 * 1000 * 1000))
# ── Logging ───────────────────────────────────────────────────────────────────
exec > >(tee -a "$LOG") 2>&1
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
die() {
log "ERROR: $*"
exit 1
}
# ── Argument parsing ─────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--disk) DISK="$2"; shift 2 ;;
--data-disk) DATA_DISK="$2"; shift 2 ;;
--role) ROLE="$2"; shift 2 ;;
--deploy-key) DEPLOY_KEY="$2"; shift 2 ;;
--headscale-server) HEADSCALE_SERVER="$2"; shift 2 ;;
--headscale-key) HEADSCALE_KEY="$2"; shift 2 ;;
-h|--help)
usage
exit 0
;;
*) die "Unknown argument: $1" ;;
esac
done
# ── Validate required arguments ───────────────────────────────────────────────
[[ -n "$DISK" ]] || die "--disk is required"
case "$ROLE" in
server|desktop|node) ;;
*) die "--role must be one of: server, desktop, node" ;;
esac
# ── Validate disk existence and size ─────────────────────────────────────────
log "=== Validating disks ==="
[[ -b "$DISK" ]] || die "OS disk not found: $DISK"
disk_size_bytes() {
local dev="$1"
lsblk -b -dno SIZE "$dev" 2>/dev/null || echo 0
}
OS_SIZE=$(disk_size_bytes "$DISK")
log "OS disk $DISK: $OS_SIZE bytes"
[[ "$OS_SIZE" -ge "$BYTES_256GB" ]] \
|| die "OS disk $DISK is too small ($(( OS_SIZE / 1024 / 1024 / 1024 )) GB). Minimum is 256 GB."
if [[ -n "$DATA_DISK" ]]; then
[[ -b "$DATA_DISK" ]] || die "Data disk not found: $DATA_DISK"
[[ "$DATA_DISK" != "$DISK" ]] || die "OS disk and data disk cannot be the same device"
DATA_SIZE=$(disk_size_bytes "$DATA_DISK")
log "Data disk $DATA_DISK: $DATA_SIZE bytes"
[[ "$DATA_SIZE" -ge "$BYTES_2TB" ]] \
|| die "Data disk $DATA_DISK is too small ($(( DATA_SIZE / 1024 / 1024 / 1024 )) GB). Minimum is 2 TB."
fi
# ── Helper: partition suffix ──────────────────────────────────────────────────
part_suffix() {
local dev="$1" n="$2"
if [[ "$dev" == *nvme* ]]; then
echo "${dev}p${n}"
else
echo "${dev}${n}"
fi
}
# ── Detect existing Bitcoin timechain data on data disk ───────────────────────
if [[ -n "$DATA_DISK" ]]; then
DATA_P1=$(part_suffix "$DATA_DISK" 1)
if [[ -b "$DATA_P1" ]]; then
DATA_LABEL=$(lsblk -no LABEL "$DATA_P1" 2>/dev/null | head -n1 || true)
if [[ -z "$DATA_LABEL" ]]; then
DATA_LABEL=$(blkid -o value -s LABEL "$DATA_P1" 2>/dev/null || true)
fi
if [[ "$DATA_LABEL" == "BTCEcoandBackup" ]]; then
CHECK_MOUNT=$(mktemp -d /tmp/sovran-data-check.XXXXXX)
if mount -o ro "$DATA_P1" "$CHECK_MOUNT" 2>/dev/null; then
if [[ -d "$CHECK_MOUNT/BTCEcoandBackup/Bitcoin_Node" && -d "$CHECK_MOUNT/BTCEcoandBackup/Electrs_Data" ]]; then
DATA_DISK_HAS_TIMECHAIN=true
log "Existing Bitcoin timechain detected on data drive — preserving data"
fi
umount "$CHECK_MOUNT" || true
fi
rmdir "$CHECK_MOUNT" 2>/dev/null || true
fi
fi
fi
# ── Step 1: Wipe disks ────────────────────────────────────────────────────────
log "=== Wiping disk(s) ==="
sgdisk --zap-all "$DISK"
wipefs --all --force "$DISK"
if [[ -n "$DATA_DISK" && "$DATA_DISK_HAS_TIMECHAIN" != true ]]; then
sgdisk --zap-all "$DATA_DISK"
wipefs --all --force "$DATA_DISK"
fi
partprobe "$DISK"
[[ -n "$DATA_DISK" && "$DATA_DISK_HAS_TIMECHAIN" != true ]] && partprobe "$DATA_DISK"
sleep 2
# ── Step 2: Partition OS disk ─────────────────────────────────────────────────
log "=== Partitioning OS disk ==="
sgdisk \
-n "1:1M:+512M" -t "1:EF00" -c "1:ESP" \
-n "2:0:0" -t "2:8300" -c "2:root" \
"$DISK"
partprobe "$DISK"
sleep 2
# ── Step 3: Partition data disk (if present) ──────────────────────────────────
if [[ -n "$DATA_DISK" && "$DATA_DISK_HAS_TIMECHAIN" != true ]]; then
log "=== Partitioning data disk ==="
sgdisk \
-n "1:1M:0" -t "1:8300" -c "1:primary" \
"$DATA_DISK"
partprobe "$DATA_DISK"
sleep 2
fi
# ── Step 4: Format partitions ─────────────────────────────────────────────────
log "=== Formatting partitions ==="
BOOT_P1=$(part_suffix "$DISK" 1)
BOOT_P2=$(part_suffix "$DISK" 2)
mkfs.vfat -F 32 "$BOOT_P1"
mkfs.ext4 -F -L sovran_systemsos "$BOOT_P2"
if [[ -n "$DATA_DISK" && "$DATA_DISK_HAS_TIMECHAIN" != true ]]; then
DATA_P1=$(part_suffix "$DATA_DISK" 1)
mkfs.ext4 -F -L BTCEcoandBackup "$DATA_P1"
fi
# ── Step 5: Mount filesystems ─────────────────────────────────────────────────
log "=== Mounting filesystems ==="
mount "$BOOT_P2" /mnt
mkdir -p /mnt/boot/efi
mount -o umask=0077,defaults "$BOOT_P1" /mnt/boot/efi
if [[ -n "$DATA_DISK" ]]; then
DATA_P1=$(part_suffix "$DATA_DISK" 1)
mkdir -p /mnt/run/media/Second_Drive
mount "$DATA_P1" /mnt/run/media/Second_Drive
# ── Step 6: Create Bitcoin data directories ─────────────────────────────
log "=== Creating Bitcoin data directories ==="
mkdir -p /mnt/run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node
mkdir -p /mnt/run/media/Second_Drive/BTCEcoandBackup/Electrs_Data
mkdir -p /mnt/run/media/Second_Drive/BTCEcoandBackup/NixOS_Snapshot_Backup
fi
# ── Step 7: Generate hardware config ─────────────────────────────────────────
log "=== Generating hardware config ==="
nixos-generate-config --root /mnt
# ── Step 8: Copy flake source ─────────────────────────────────────────────────
log "=== Copying flake to /mnt ==="
cp /mnt/etc/nixos/hardware-configuration.nix /tmp/hardware-configuration.nix
rm -rf /mnt/etc/nixos/
mkdir -p /mnt/etc/nixos
cp -a "${FLAKE}/." /mnt/etc/nixos/
cp /tmp/hardware-configuration.nix /mnt/etc/nixos/hardware-configuration.nix
# ── Step 9: Write role-state.nix ─────────────────────────────────────────────
log "=== Writing role config ==="
case "$ROLE" in
server)
IS_SERVER=true; IS_DESKTOP=false; IS_NODE=false ;;
desktop)
IS_SERVER=false; IS_DESKTOP=true; IS_NODE=false ;;
node)
IS_SERVER=false; IS_DESKTOP=false; IS_NODE=true ;;
esac
cat > /mnt/etc/nixos/role-state.nix <<EOF
# THIS FILE IS AUTO-GENERATED BY THE INSTALLER. DO NOT EDIT.
{ config, lib, ... }:
{
sovran_systemsOS.roles.server_plus_desktop = lib.mkDefault ${IS_SERVER};
sovran_systemsOS.roles.desktop = lib.mkDefault ${IS_DESKTOP};
sovran_systemsOS.roles.node = lib.mkDefault ${IS_NODE};
}
EOF
# ── Step 10: Write custom.nix with deploy config ──────────────────────────────
log "=== Writing custom.nix ==="
if [[ -n "$DEPLOY_KEY" || -n "$HEADSCALE_SERVER" ]]; then
{
echo '{ config, lib, ... }:'
echo '{'
echo ' sovran_systemsOS.deploy = {'
echo ' enable = true;'
[[ -n "$DEPLOY_KEY" ]] && echo " authorizedKey = \"${DEPLOY_KEY}\";"
[[ -n "$HEADSCALE_SERVER" ]] && echo " headscaleServer = \"${HEADSCALE_SERVER}\";"
echo ' };'
echo '}'
} > /mnt/etc/nixos/custom.nix
else
cp /mnt/etc/nixos/custom.template.nix /mnt/etc/nixos/custom.nix
chmod 644 /mnt/etc/nixos/custom.nix
fi
# ── Write Headscale auth key if provided ─────────────────────────────────────
if [[ -n "$HEADSCALE_KEY" ]]; then
mkdir -p /mnt/var/lib/secrets
echo "$HEADSCALE_KEY" > /mnt/var/lib/secrets/headscale-authkey
chmod 600 /mnt/var/lib/secrets/headscale-authkey
log "Headscale auth key written to /mnt/var/lib/secrets/headscale-authkey"
fi
# ── Step 11: Copy configs to host for flake evaluation ───────────────────────
log "=== Copying config files to host /etc/nixos for flake evaluation ==="
mkdir -p /etc/nixos
cp /mnt/etc/nixos/role-state.nix /etc/nixos/role-state.nix
cp /mnt/etc/nixos/custom.nix /etc/nixos/custom.nix
cp /mnt/etc/nixos/hardware-configuration.nix /etc/nixos/hardware-configuration.nix
# ── Step 12: Run nixos-install ────────────────────────────────────────────────
log "=== Running nixos-install ==="
nixos-install \
--root /mnt \
--flake /mnt/etc/nixos#nixos \
--no-root-password \
--impure
log "=== Installation complete! ==="
log "You can now reboot into Sovran_SystemsOS."
log "After reboot, the machine will be accessible via SSH on port 22 (if --deploy-key was provided)."
[[ -n "$HEADSCALE_SERVER" ]] && \
log "Tailscale will connect to Headscale at ${HEADSCALE_SERVER} on first boot."
+24
View File
@@ -0,0 +1,24 @@
{config, pkgs, lib, ...}:
{
systemd.services.Sovran_SystemsOS_File_Fixes_And_New_Services = {
unitConfig = {
After = "btcpayserver.service";
Requires = "network-online.target";
};
serviceConfig = {
ExecStartPre= "/run/current-system/sw/bin/sleep 30";
ExecStart = "/run/current-system/sw/bin/wget https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/raw/branch/main/file_fixes_and_new_services/Sovran_SystemsOS_File_Fixes_And_New_Services.sh -O /home/free/Downloads/Sovran_SystemsOS_File_Fixes_And_New_Services.sh ; /run/current-system/sw/bin/bash /home/free/Downloads/Sovran_SystemsOS_File_Fixes_And_New_Services.sh";
RemainAfterExit = "yes";
User = "root";
Type = "oneshot";
};
wantedBy = [ "multi-user.target" ];
};
}
+2
View File
@@ -4,12 +4,14 @@ let
cfg = config.sovran_systemsOS;
in
{
# ✅ Option definition
options.sovran_systemsOS.packages.bip110 = lib.mkOption {
type = lib.types.nullOr lib.types.package;
default = null;
description = "BIP110 Bitcoin package";
};
# ✅ Implementation
config = lib.mkIf (
cfg.features.bip110 &&
cfg.packages.bip110 != null
+24 -45
View File
@@ -1,6 +1,8 @@
{ config, pkgs, lib, ... }:
lib.mkIf config.sovran_systemsOS.services.bitcoin {
lib.mkIf config.sovran_systemsOS.features.bitcoin {
## Bitcoind
services.bitcoind = {
enable = true;
@@ -20,12 +22,18 @@ lib.mkIf config.sovran_systemsOS.services.bitcoin {
nix-bitcoin.onionServices.electrs.enable = true;
nix-bitcoin.onionServices.rtl.enable = true;
## Electrs
services.electrs = {
enable = true;
tor.enforce = true;
dataDir = "/run/media/Second_Drive/BTCEcoandBackup/Electrs_Data";
};
## LND
services.lnd = {
enable = true;
tor.enforce = true;
@@ -37,11 +45,17 @@ lib.mkIf config.sovran_systemsOS.services.bitcoin {
nix-bitcoin.onionServices.lnd.public = true;
## LNDconnect
services.lnd.lndconnect = {
enable = true;
onion = true;
};
## RTL
services.rtl = {
enable = true;
tor.enforce = true;
@@ -51,16 +65,24 @@ lib.mkIf config.sovran_systemsOS.services.bitcoin {
lnd = {
enable = true;
};
};
};
## BTCpayserver
services.btcpayserver = {
enable = config.sovran_systemsOS.web.btcpayserver;
enable = true;
};
services.btcpayserver.lightningBackend = "lnd";
## System
nix-bitcoin.generateSecrets = true;
nix-bitcoin.nodeinfo.enable = true;
nix-bitcoin.operator = {
@@ -70,47 +92,4 @@ lib.mkIf config.sovran_systemsOS.services.bitcoin {
nix-bitcoin.useVersionLockedPkgs = false;
systemd.services.bitcoind = {
requires = [ "run-media-Second_Drive.mount" ];
after = [ "run-media-Second_Drive.mount" ];
serviceConfig.PrivateUsers = lib.mkForce false;
};
systemd.services.electrs = {
requires = lib.mkForce [ "run-media-Second_Drive.mount" ];
after = [ "run-media-Second_Drive.mount" "bitcoind.service" ];
wants = [ "bitcoind.service" ];
};
systemd.services.lnd = {
wants = [ "bitcoind.service" ];
# requires for bitcoind set by nix-bitcoin; mkForce removes it
requires = lib.mkForce [ ];
};
systemd.services.sovran-btc-permissions = {
description = "Fix Bitcoin/Electrs data directory ownership on second drive";
wantedBy = [ "multi-user.target" ];
after = [ "run-media-Second_Drive.mount" ];
before = [ "bitcoind.service" "electrs.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
if [ -d /run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node ]; then
chown -R bitcoin:bitcoin /run/media/Second_Drive/BTCEcoandBackup/Bitcoin_Node
fi
if [ -d /run/media/Second_Drive/BTCEcoandBackup/Electrs_Data ]; then
chown -R electrs:electrs /run/media/Second_Drive/BTCEcoandBackup/Electrs_Data
fi
'';
};
networking.firewall.allowedTCPPorts = [ 3051 ];
networking.firewall.allowedUDPPorts = [ 3051 ];
sovran_systemsOS.domainRequirements = [
{ name = "btcpayserver"; label = "BTCPay Server"; example = "pay.yourdomain.com"; }
];
}
-224
View File
@@ -1,224 +0,0 @@
{ config, pkgs, lib, ... }:
let
exposeBtcpay = config.sovran_systemsOS.web.btcpayserver;
extraVhosts = config.sovran_systemsOS.caddy.extraVirtualHosts;
# True when any service needs HTTPS/ACME (domain-based vhosts)
needsHttpsPorts =
config.sovran_systemsOS.web.btcpayserver
|| config.sovran_systemsOS.services.synapse
|| config.sovran_systemsOS.services.wordpress
|| config.sovran_systemsOS.services.nextcloud
|| config.sovran_systemsOS.services.vaultwarden
|| config.sovran_systemsOS.features.haven
|| config.sovran_systemsOS.features.element-calling;
in
{
services.caddy = {
enable = true;
user = "caddy";
group = "root";
};
# Only open ports 80/443 when at least one domain-based service is active
networking.firewall.allowedTCPPorts = lib.mkIf needsHttpsPorts [ 80 443 ];
networking.firewall.allowedUDPPorts = lib.mkIf needsHttpsPorts [ 80 443 ];
systemd.tmpfiles.rules = [
"d /var/lib/domains 0755 caddy root -"
];
# Override ExecStart + ExecReload to point at the runtime-generated Caddyfile
systemd.services.caddy.serviceConfig = {
ExecStart = lib.mkForce [
""
"${pkgs.caddy}/bin/caddy run --config /run/caddy/Caddyfile --adapter caddyfile"
];
ExecReload = lib.mkForce [
""
"${pkgs.caddy}/bin/caddy reload --config /run/caddy/Caddyfile --adapter caddyfile --force"
];
};
systemd.services.caddy-generate-config = {
description = "Generate Caddyfile from /var/lib/domains at runtime";
before = [ "caddy.service" ];
requiredBy = [ "caddy.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "caddy";
};
path = [ pkgs.coreutils ];
script = ''
read_domain() {
if [ -f "/var/lib/domains/$1" ]; then
cat "/var/lib/domains/$1"
else
echo ""
fi
}
MATRIX=$(read_domain matrix)
WORDPRESS=$(read_domain wordpress)
NEXTCLOUD=$(read_domain nextcloud)
BTCPAY=$(read_domain btcpayserver)
VAULTWARDEN=$(read_domain vaultwarden)
HAVEN=$(read_domain haven)
ACME_EMAIL=$(read_domain sslemail)
# Start with global config use ACME only when domain-based services are active
${if needsHttpsPorts then ''
cat > /run/caddy/Caddyfile <<EOF
{
email $ACME_EMAIL
}
EOF
'' else ''
cat > /run/caddy/Caddyfile <<EOF
{
auto_https off
}
EOF
''}
# Matrix
if [ -n "$MATRIX" ]; then
if [ -f /run/caddy/element-calling.snippet ]; then
cat /run/caddy/element-calling.snippet >> /run/caddy/Caddyfile
else
cat >> /run/caddy/Caddyfile <<EOF
$MATRIX {
reverse_proxy /_matrix/* http://localhost:8008
reverse_proxy /_synapse/client/* http://localhost:8008
}
$MATRIX:8448 {
reverse_proxy http://localhost:8008
}
EOF
fi
fi
# WordPress
if [ -n "$WORDPRESS" ]; then
cat >> /run/caddy/Caddyfile <<EOF
$WORDPRESS {
encode gzip zstd
root * /var/lib/www/wordpress
php_fastcgi unix//run/phpfpm/wordpress.sock
file_server browse
}
EOF
fi
# Nextcloud
if [ -n "$NEXTCLOUD" ]; then
cat >> /run/caddy/Caddyfile <<EOF
$NEXTCLOUD {
encode gzip zstd
root * /var/lib/www/nextcloud
php_fastcgi unix//run/phpfpm/nextcloud.sock {
trusted_proxies private_ranges
}
file_server
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
header {
Strict-Transport-Security max-age=31536000;
}
}
EOF
fi
# BTCPay (only if web exposure is enabled)
${if exposeBtcpay then ''
if [ -n "$BTCPAY" ]; then
cat >> /run/caddy/Caddyfile <<EOF
$BTCPAY {
reverse_proxy http://localhost:23000
encode gzip zstd
}
EOF
fi
'' else ''
# BTCPay web exposure disabled by sovran_systemsOS.web.btcpayserver = false
''}
# Vaultwarden
if [ -n "$VAULTWARDEN" ]; then
cat >> /run/caddy/Caddyfile <<EOF
$VAULTWARDEN {
reverse_proxy http://localhost:8777
encode gzip zstd
}
EOF
fi
# Haven
if [ -n "$HAVEN" ]; then
cat >> /run/caddy/Caddyfile <<EOF
$HAVEN {
reverse_proxy localhost:3355 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
transport http {
versions 1.1
}
}
request_body {
max_size 100MB
}
}
EOF
fi
# Sovran Hub (LAN access via mDNS)
cat >> /run/caddy/Caddyfile <<EOF
http://sovransystemsos.local {
reverse_proxy localhost:8937
header {
Clear-Site-Data "\"cache\""
Cache-Control "no-store, no-cache, must-revalidate, max-age=0"
Pragma "no-cache"
Expires "0"
}
}
EOF
# RTL (LAN access)
cat >> /run/caddy/Caddyfile <<EOF
:3051 {
reverse_proxy :3050
encode gzip zstd
}
EOF
# Mempool (LAN access)
cat >> /run/caddy/Caddyfile <<EOF
:60847 {
reverse_proxy :60845
encode gzip zstd
}
EOF
# Custom vhosts from custom.nix
cat >> /run/caddy/Caddyfile <<'CUSTOM_VHOSTS_EOF'
${extraVhosts}
CUSTOM_VHOSTS_EOF
'';
};
}
-52
View File
@@ -1,52 +0,0 @@
# ── modules/core/cpu-performance.nix ──────────────────────────────────────────
# Forces all CPU cores to run at maximum frequency on node and server_plus_desktop
# roles. Desktop-only installs retain normal OS power management behaviour.
#
# Three layers:
# 1. power-profiles-daemon disabled — removes the GNOME power profile picker;
# no user can switch profiles
# 2. cpufreq performance governor — pins every core to max frequency via
# kernel, enforced at boot by a oneshot unit
# 3. systemd oneshot enforcement — belt-and-suspenders; applies the governor
# after every boot even if module loads late
{ config, lib, pkgs, ... }:
{
config = lib.mkIf (!config.sovran_systemsOS.roles.desktop) {
# ── Layer 1: disable power-profiles-daemon ───────────────────────────────
# This removes the power-profile switcher from GNOME Settings entirely.
services.power-profiles-daemon.enable = false;
# ── Layer 2: set cpufreq governor to performance ─────────────────────────
# Pins all cores to max frequency. Works on Intel (intel_pstate) and AMD
# (amd-pstate / acpi-cpufreq) alike.
powerManagement.cpuFreqGovernor = "performance";
# ── Layer 3: enforce at boot via systemd oneshot ─────────────────────────
# Belt-and-suspenders: ensures the governor is applied after every boot even
# if the kernel module loads late.
systemd.services.cpu-performance = {
description = "Set CPU governor to performance on all cores";
wantedBy = [ "multi-user.target" ];
after = [ "systemd-modules-load.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
found=0
for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
if [ -w "$gov" ]; then
echo performance > "$gov"
found=1
fi
done
if [ "$found" -eq 0 ]; then
echo "cpu-performance: no writable cpufreq governors found (VM or unsupported hardware)" >&2
fi
'';
};
};
}
-33
View File
@@ -1,33 +0,0 @@
{ config, lib, pkgs, ... }:
{
# ── Legacy Cleanup ─────────────────────────────────────────────
# Removes deprecated apps and folders from the old Sovran_Systems
# repo that are no longer needed under staging_alpha.
# This runs on every activation but is idempotent (no-ops if
# the files are already gone).
system.activationScripts.cleanupLegacySovran = lib.stringAfter [ "users" ] ''
echo " Cleaning up legacy Sovran_Systems artifacts "
# Remove deprecated .desktop files
for f in \
/home/free/.local/share/applications/Sovran_SystemsOS_External_Backup.desktop \
/home/free/.local/share/applications/Sovran_SystemsOS_Updater.desktop \
/home/free/.local/share/applications/Sovran_SystemsOS_Resetter.desktop
do
if [ -f "$f" ]; then
rm -f "$f"
echo " Removed: $f"
fi
done
# Remove legacy Sovran_Systems folder (skip if it's a symlink)
if [ -d /home/free/.Sovran_Systems ] && [ ! -L /home/free/.Sovran_Systems ]; then
rm -rf /home/free/.Sovran_Systems
echo " Removed: /home/free/.Sovran_Systems/"
fi
echo " Legacy cleanup complete "
'';
}
-32
View File
@@ -1,32 +0,0 @@
{ config, pkgs, lib, ... }:
{
# ── Ensure njalla directory and base script exist on every build ──
systemd.tmpfiles.rules = [
"d /var/lib/njalla 0750 root root -"
];
# ── Create base njalla.sh if it doesn't exist yet ────────────
systemd.services.njalla-init = {
description = "Initialize Njal.la DDNS script if missing";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
unitConfig = {
ConditionPathExists = "!/var/lib/njalla/njalla.sh";
};
script = ''
cat > /var/lib/njalla/njalla.sh <<'SCRIPT'
#!/usr/bin/env bash
IP=$(dig @resolver4.opendns.com myip.opendns.com +short -4)
## Add DDNS entries below one curl per line
## Managed via Sovran Hub web interface
SCRIPT
chmod 700 /var/lib/njalla/njalla.sh
'';
};
}
-40
View File
@@ -1,40 +0,0 @@
# ── modules/core/no-sleep.nix ─────────────────────────────────────────────────
# Prevents the machine from ever sleeping or suspending at the system level.
#
# Only applies to server_plus_desktop and node roles. Desktop-only installs
# retain normal OS sleep/suspend behaviour.
#
# This operates at two layers below GNOME:
# 1. systemd-logind — ignores all hardware power events (lid, suspend key, etc.)
# 2. systemd targets — masks sleep/suspend/hibernate targets so nothing can
# trigger them, not even `systemctl suspend` or D-Bus calls.
#
# This is intentional for a 24/7 server/node. The GNOME-layer power settings in
# sovran_systemsos-desktop.nix remain in place as a belt-and-suspenders complement
# for active user sessions.
{ config, lib, ... }:
{
config = lib.mkIf (!config.sovran_systemsOS.roles.desktop) {
# ── Layer 1: logind hardware event handling ──────────────────────────────
services.logind.settings.Login = {
HandleLidSwitch = "ignore";
HandleLidSwitchDocked = "ignore";
HandleLidSwitchExternalPower = "ignore";
HandleSuspendKey = "ignore";
HandleHibernateKey = "ignore";
HandlePowerKey = "ignore";
IdleAction = "ignore";
IdleActionSec = 0;
};
# ── Layer 2: mask systemd sleep targets ─────────────────────────────────
# Nothing on the system can suspend/hibernate — not root, not GNOME, not D-Bus.
systemd.targets.sleep.enable = false;
systemd.targets.suspend.enable = false;
systemd.targets.hibernate.enable = false;
systemd.targets.hybrid-sleep.enable = false;
};
}
-121
View File
@@ -1,121 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.sovran_systemsOS.deploy;
in
{
options.sovran_systemsOS.deploy = {
enable = lib.mkEnableOption "Remote deploy mode";
authorizedKey = lib.mkOption {
type = lib.types.str;
default = "";
description = "Deployer's SSH public key for root access";
};
headscaleServer = lib.mkOption {
type = lib.types.str;
default = "";
description = "Headscale login server URL (e.g. https://hs.sovransystems.com). If set, Tailscale is used for post-install connectivity.";
};
headscaleAuthKeyFile = lib.mkOption {
type = lib.types.str;
default = "/var/lib/secrets/headscale-authkey";
description = "Path to file containing the Headscale pre-auth key for post-install enrollment";
};
};
config = lib.mkIf cfg.enable {
# ── Force SSH open on all interfaces ────────────────────────────────────
services.openssh = {
enable = true;
listenAddresses = lib.mkForce [
{ addr = "0.0.0.0"; port = 22; }
{ addr = "127.0.0.1"; port = 22; }
];
settings = {
PermitRootLogin = lib.mkForce "prohibit-password";
PasswordAuthentication = lib.mkForce false;
};
};
networking.firewall.allowedTCPPorts = [ 22 ];
# ── Inject deployer's SSH public key into root's authorized keys ─────────
users.users.root.openssh.authorizedKeys.keys =
lib.mkIf (cfg.authorizedKey != "") [ cfg.authorizedKey ];
# ── Force RDP on ─────────────────────────────────────────────────────────
sovran_systemsOS.features.rdp = lib.mkForce true;
# ── Enable Fail2Ban for SSH protection ───────────────────────────────────
services.fail2ban = {
enable = true;
ignoreIP = [ "127.0.0.0/8" ];
};
# ── Tailscale / Headscale VPN (only when headscaleServer is configured) ──
services.tailscale = lib.mkIf (cfg.headscaleServer != "") {
enable = true;
};
environment.systemPackages = lib.mkIf (cfg.headscaleServer != "") [ pkgs.tailscale ];
systemd.services.deploy-tailscale-connect = lib.mkIf (cfg.headscaleServer != "") {
description = "Connect to Headscale Tailnet for post-install remote access";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" "tailscaled.service" ];
wants = [ "network-online.target" "tailscaled.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
AUTH_KEY_FILE="${cfg.headscaleAuthKeyFile}"
if [ ! -f "$AUTH_KEY_FILE" ]; then
echo "Headscale auth key file not found: $AUTH_KEY_FILE skipping Tailscale enrollment"
exit 0
fi
AUTH_KEY=$(cat "$AUTH_KEY_FILE")
[ -n "$AUTH_KEY" ] || { echo "Auth key file is empty, skipping"; exit 0; }
HOSTNAME_SUFFIX=$(hostname | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g; s/-\{2,\}/-/g; s/^-//; s/-$//')
HOSTNAME="sovran-$HOSTNAME_SUFFIX"
echo "Joining Tailnet via ${cfg.headscaleServer} as $HOSTNAME..."
${pkgs.tailscale}/bin/tailscale up \
--login-server="${cfg.headscaleServer}" \
--authkey="$AUTH_KEY" \
--hostname="$HOSTNAME"
echo "Tailscale IP: $(${pkgs.tailscale}/bin/tailscale ip -4 2>/dev/null || echo 'pending')"
'';
path = [ pkgs.tailscale pkgs.coreutils ];
};
# ── Safety auto-expiry service ────────────────────────────────────────────
systemd.services.deploy-auto-expire = {
description = "Auto-expire remote deploy mode after 48 hours";
wantedBy = [ "multi-user.target" ];
after = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = false;
};
script = ''
# 48 hours = 172800 seconds
sleep $((48 * 60 * 60))
systemctl stop deploy-tailscale-connect || true
mkdir -p /etc/sovran
echo "expired" > /etc/sovran/deploy-mode
'';
path = [ pkgs.coreutils ];
};
# ── Deploy-mode indicator file ────────────────────────────────────────────
environment.etc."sovran/deploy-mode".text = "active";
};
}
+25 -34
View File
@@ -3,43 +3,34 @@
{
config = lib.mkMerge [
# ── Server+Desktop Role (default) ─────────────────────────
(lib.mkIf config.sovran_systemsOS.roles.server_plus_desktop {
sovran_systemsOS.web.btcpayserver = lib.mkDefault true;
})
# ── Desktop Only Role ─────────────────────────────────────
(lib.mkIf config.sovran_systemsOS.roles.desktop {
services.desktopManager.gnome.enable = true;
sovran_systemsOS.services = {
synapse = lib.mkDefault false;
bitcoin = lib.mkDefault false;
vaultwarden = lib.mkDefault false;
wordpress = lib.mkDefault false;
nextcloud = lib.mkDefault false;
};
sovran_systemsOS.web.btcpayserver = lib.mkDefault false;
})
# ── Bitcoin Node Only Role ────────────────────────────────
# Bitcoin ecosystem + mempool + bip110, BTCPay runs but not exposed via Caddy
(lib.mkIf config.sovran_systemsOS.roles.node {
sovran_systemsOS.services = {
bitcoin = lib.mkDefault true;
synapse = lib.mkDefault false;
vaultwarden = lib.mkDefault false;
wordpress = lib.mkDefault false;
nextcloud = lib.mkDefault false;
};
# Server-Desktop Role most services enabled
(lib.mkIf config.sovran_systemsOS.roles.server-desktop {
sovran_systemsOS.features = {
mempool = lib.mkDefault true;
bip110 = lib.mkDefault true;
synapse = true;
bitcoin = true;
coturn = true;
vaultwarden = true;
haven = false;
mempool = false;
bip110 = false;
element-calling = false;
bitcoin-core = false;
rdp = false;
};
})
sovran_systemsOS.web.btcpayserver = lib.mkDefault false;
# Desktop role
(lib.mkIf config.sovran_systemsOS.roles.desktop {
services.xserver.enable = true;
services.desktopManager.gnome.enable = true;
})
# Bitcoin node role
(lib.mkIf config.sovran_systemsOS.roles.node {
sovran_systemsOS.features = {
bitcoin = true;
bip110 = false;
};
})
];
+5 -64
View File
@@ -3,7 +3,7 @@
{
options.sovran_systemsOS = {
roles = {
server_plus_desktop = lib.mkOption {
server-desktop = lib.mkOption {
type = lib.types.bool;
default = !config.sovran_systemsOS.roles.desktop && !config.sovran_systemsOS.roles.node;
};
@@ -11,76 +11,17 @@
node = lib.mkEnableOption "Bitcoin Node Only Role";
};
# ── Services (default ON — user can disable in custom.nix) ──
services = {
synapse = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Matrix Synapse homeserver";
};
bitcoin = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Bitcoin Ecosystem (bitcoind, electrs, lnd, rtl, btcpay)";
};
vaultwarden = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Vaultwarden password manager";
};
wordpress = lib.mkOption {
type = lib.types.bool;
default = true;
description = "WordPress (raw PHP served by Caddy)";
};
nextcloud = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Nextcloud (raw PHP served by Caddy)";
};
};
# ── Features (default OFF — user can enable in custom.nix) ──
features = {
coturn = lib.mkEnableOption "TURN server";
synapse = lib.mkEnableOption "Matrix Synapse";
bitcoin = lib.mkEnableOption "Bitcoin Ecosystem";
vaultwarden = lib.mkEnableOption "Vaultwarden";
haven = lib.mkEnableOption "Haven NOSTR relay";
bip110 = lib.mkEnableOption "BIP-110 Bitcoin Better Money";
mempool = lib.mkEnableOption "Bitcoin Mempool Explorer";
element-calling = lib.mkEnableOption "Element Video and Audio Calling";
bitcoin-core = lib.mkEnableOption "Bitcoin Core";
rdp = lib.mkEnableOption "Gnome Remote Desktop";
sshd = lib.mkEnableOption "SSH remote access";
};
# ── Web exposure (controls Caddy vhosts) ──────────────────
web = {
btcpayserver = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Expose BTCPay Server via Caddy";
};
};
# ── Caddy customisation ───────────────────────────────────
caddy = {
extraVirtualHosts = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Additional raw Caddyfile blocks appended to the generated Caddy config. Use this in custom.nix to add custom domains and reverse proxies.";
};
};
# ── Domain setup registry ─────────────────────────────────
domainRequirements = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
name = lib.mkOption { type = lib.types.str; };
label = lib.mkOption { type = lib.types.str; };
example = lib.mkOption { type = lib.types.str; };
needsDDNS = lib.mkOption { type = lib.types.bool; default = true; };
};
});
default = [];
description = "Domain requirements registered by each module";
};
nostr_npub = lib.mkOption {

Some files were not shown because too many files have changed in this diff Show More