Compare commits

Author SHA1 Message Date
renovate 0ab30379f0 Update golang.org/x/crypto/x509roots/fallback digest to 7a4a4d6
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-21 08:02:27 +00:00
alazyreader 3ef4219999 Merge pull request 'Update module tailscale.com to v1.102.4' (#128) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #128
2026-09-14 03:42:02 +00:00
renovate 23f5e54973 Update module tailscale.com to v1.102.4
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-10 19:02:12 +00:00
alazyreader 7d3b69cd7e Merge pull request 'Update module golang.org/x/sync to v0.23.0' (#126) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #126
2026-09-08 14:40:57 +00:00
renovate 4a4a89ac9c Update module golang.org/x/sync to v0.23.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-08 13:01:45 +00:00
alazyreader ebaef096fd Merge pull request 'Update golang.org/x/crypto/x509roots/fallback digest to 86efde5' (#125) from renovate/golang.org-x-crypto-x509roots-fallback-digest into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #125
2026-09-07 03:46:43 +00:00
renovate c4ae501402 Update golang.org/x/crypto/x509roots/fallback digest to 86efde5
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-06 20:01:47 +00:00
alazyreader 23a39f07bb Merge pull request 'fix cert issue' (#124) from add-certs into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #124
2026-09-06 19:59:34 +00:00
alazyreader 43158a0661 fix cert issue
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-06 15:58:54 -04:00
alazyreader dce67813d9 Merge pull request 'Update mysql Docker tag to v26' (#118) from renovate/mysql-26.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #118
2026-09-05 20:01:59 +00:00
alazyreader bce3730a91 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.10.1' (#123) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #123
2026-09-02 19:52:17 +00:00
renovate 65bb611bb5 Update module github.com/go-sql-driver/mysql to v1.10.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-02 10:02:32 +00:00
alazyreader 3c5108c947 Merge pull request 'Update module tailscale.com to v1.102.3' (#121) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #121
2026-08-20 04:28:53 +00:00
renovate 7ec55823e2 Update module tailscale.com to v1.102.3
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-20 04:02:26 +00:00
alazyreader a5c9ff743f Merge pull request 'Update golang Docker tag to v1.27' (#122) from renovate/golang-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #122
2026-08-20 03:58:38 +00:00
alazyreader e51b9235f1 upgrade gomod
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-19 23:50:16 -04:00
renovate 4b0b906462 Update golang Docker tag to v1.27
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-19 20:03:18 +00:00
alazyreader 50e3c6c7d0 Merge pull request 'Update module tailscale.com to v1.102.2' (#120) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #120
2026-08-05 05:20:16 +00:00
renovate 72be469f49 Update module tailscale.com to v1.102.2
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-04 22:02:04 +00:00
alazyreader 203581f37c Merge pull request 'Update module tailscale.com to v1.102.1' (#119) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #119
2026-08-01 12:17:32 +00:00
renovate 99df476d0f Update module tailscale.com to v1.102.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-31 18:02:06 +00:00
renovate 2568f159ff Update mysql Docker tag to v26
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-28 00:01:44 +00:00
alazyreader 648a36b608 Merge pull request 'Update module tailscale.com to v1.102.0' (#117) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #117
2026-07-27 02:22:35 +00:00
renovate b47c7909cf Update module tailscale.com to v1.102.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-23 19:02:01 +00:00
alazyreader cbfc7fe4bf Merge pull request 'Update module golang.org/x/sync to v0.22.0' (#116) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #116
2026-07-08 14:21:22 +00:00
renovate 0144dab073 Update module golang.org/x/sync to v0.22.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 13:01:56 +00:00
alazyreader 46d82dafd2 Merge pull request 'Update module golang.org/x/sync to v0.21.0' (#115) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #115
2026-06-08 14:27:18 +00:00
renovate 6166f1462f Update module golang.org/x/sync to v0.21.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-06-08 13:01:59 +00:00
alazyreader d8edfeda3c Merge pull request 'Update module tailscale.com to v1.100.0' (#114) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #114
2026-06-04 21:19:58 +00:00
renovate 702ecd21e0 Update module tailscale.com to v1.100.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-06-03 20:02:12 +00:00
alazyreader 5d8abc9bc6 Merge pull request 'Update module tailscale.com to v1.98.5' (#113) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #113
2026-05-31 19:02:33 +00:00
renovate a14f4e975d Update module tailscale.com to v1.98.5
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-29 18:01:55 +00:00
alazyreader 1f182dae3c Merge pull request 'Update module tailscale.com to v1.98.4' (#112) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #112
2026-05-29 15:49:35 +00:00
renovate 0abc527c39 Update module tailscale.com to v1.98.4
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-28 20:02:24 +00:00
alazyreader 5bbaea73b6 Merge pull request 'Update module tailscale.com to v1.98.3' (#111) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #111
2026-05-25 17:59:19 +00:00
renovate ffd4a8b2a5 Update module tailscale.com to v1.98.3
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-21 19:02:16 +00:00
alazyreader db0afda596 Merge pull request 'Update module tailscale.com to v1.98.2' (#110) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #110
2026-05-17 20:10:16 +00:00
renovate 32007ff000 Update module tailscale.com to v1.98.2
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-14 16:03:08 +00:00
alazyreader c45a2e1057 fix a few bugs that claude pointed out
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-12 15:58:45 -04:00
alazyreader a7c5e1708c fix missing pull of edit checkboxes
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-12 15:52:20 -04:00
alazyreader 7f5fcd0943 Merge pull request 'Update module tailscale.com to v1.98.1' (#109) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #109
2026-05-08 04:28:31 +00:00
renovate 3de90072b2 Update module tailscale.com to v1.98.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-07 21:02:40 +00:00
alazyreader 437b419df5 Merge pull request 'Update module tailscale.com to v1.98.0' (#108) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #108
2026-05-06 17:56:58 +00:00
renovate c4833c6135 Update module tailscale.com to v1.98.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-05 20:01:56 +00:00
alazyreader b204005ab9 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.10.0' (#107) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #107
2026-04-29 18:49:35 +00:00
renovate 7fab8f8b27 Update module github.com/go-sql-driver/mysql to v1.10.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-29 14:02:37 +00:00
alazyreader 1f760ed33d Merge pull request 'Update mysql Docker tag to v9.7' (#106) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #106
2026-04-27 20:31:49 +00:00
renovate f66699b3f9 Update mysql Docker tag to v9.7
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-23 03:02:27 +00:00
alazyreader fe767e821d Merge pull request 'Update module tailscale.com to v1.96.5' (#105) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #105
2026-03-29 20:59:53 +00:00
renovate c67a62987d Update module tailscale.com to v1.96.5
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-27 19:01:59 +00:00
alazyreader a4c543fb5a Merge pull request 'Update module tailscale.com to v1.96.4' (#104) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #104
2026-03-26 00:46:34 +00:00
renovate b279cd53b6 Update module tailscale.com to v1.96.4
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-25 21:02:09 +00:00
alazyreader 3bfaf8c34a Merge pull request 'Update module tailscale.com to v1.96.3' (#103) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #103
2026-03-20 00:49:38 +00:00
renovate 0efba3074e Update module tailscale.com to v1.96.3
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-19 18:02:30 +00:00
alazyreader 847e363cf0 Merge pull request 'Update module tailscale.com to v1.96.2' (#102) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #102
2026-03-19 16:21:45 +00:00
renovate 463ee8d0e0 Update module tailscale.com to v1.96.2
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-17 23:02:16 +00:00
alazyreader ad8b6cc1a9 Merge pull request 'Update mysql Docker tag to v9.6' (#94) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #94
2026-03-16 15:02:25 +00:00
alazyreader 7d58ce6140 Merge pull request 'Update module tailscale.com to v1.96.1' (#101) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #101
2026-03-13 17:10:54 +00:00
renovate 030ec84b1a Update module tailscale.com to v1.96.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-10 15:02:25 +00:00
alazyreader 7cc64bbbfb Merge pull request 'Update module golang.org/x/sync to v0.20.0' (#100) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #100
2026-03-09 01:42:30 +00:00
renovate 0778530d7d Update module golang.org/x/sync to v0.20.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-08 12:02:52 +00:00
alazyreader fc972d2baa Merge pull request 'Update module tailscale.com to v1.96.0' (#99) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #99
2026-03-05 21:37:46 +00:00
renovate ab1d3e6e74 Update module tailscale.com to v1.96.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-05 21:02:21 +00:00
alazyreader d60db5f67b clean up go file
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-14 16:10:03 -05:00
alazyreader f57a971951 Merge pull request 'more-build-caching' (#98) from more-build-caching into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #98
2026-02-14 20:56:49 +00:00
alazyreader bef3300198 fix build step
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-14 15:54:10 -05:00
alazyreader 873875c33a build in CI, not in docker
ci/woodpecker/push/woodpecker Pipeline failed
2026-02-14 15:53:28 -05:00
alazyreader a679d229f8 Update .woodpecker.yml
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-14 20:44:43 +00:00
alazyreader 89b715ec9a use a build cache
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-14 05:40:18 +00:00
alazyreader a541745127 Merge pull request 'Update module tailscale.com to v1.94.2' (#97) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #97
2026-02-14 04:47:03 +00:00
renovate c2c8e87ea3 Update module tailscale.com to v1.94.2
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-13 21:02:37 +00:00
alazyreader 63d9fc5156 Merge pull request 'Update golang Docker tag to v1.26' (#96) from renovate/golang-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #96
2026-02-13 15:58:59 +00:00
renovate cf0d211fdb Update mysql Docker tag to v9.6
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-13 01:03:02 +00:00
renovate d0865afa9b Update golang Docker tag to v1.26
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-10 22:02:11 +00:00
alazyreader 5a3b036107 Merge pull request 'Update module tailscale.com to v1.94.1' (#95) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #95
2026-01-22 22:12:00 +00:00
renovate bccd669baa Update module tailscale.com to v1.94.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-22 20:02:14 +00:00
alazyreader dd05b761b3 Merge pull request 'Update module tailscale.com to v1.94.0' (#93) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #93
2026-01-18 20:03:19 +00:00
renovate 53757133fd Update module tailscale.com to v1.94.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-14 22:03:46 +00:00
alazyreader 2fc8ac37eb Merge pull request 'Update module tailscale.com to v1.92.5' (#92) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #92
2026-01-08 14:53:12 +00:00
renovate 212327b7cf Update module tailscale.com to v1.92.5
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-06 19:03:44 +00:00
alazyreader 56ffcb8453 Merge pull request 'Update module tailscale.com to v1.92.4' (#91) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #91
2025-12-26 18:11:38 +00:00
renovate 59ea015b24 Update module tailscale.com to v1.92.4
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-18 11:03:20 +00:00
alazyreader d8a3a2bf8f Merge pull request 'Update mysql Docker tag to v9.5' (#82) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #82
2025-12-14 21:04:52 +00:00
alazyreader 37f29d675b Merge pull request 'Update module tailscale.com to v1.92.2' (#90) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #90
2025-12-13 19:48:46 +00:00
renovate f3e5772f63 Update module tailscale.com to v1.92.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-10 23:03:11 +00:00
renovate 20f0890b94 Update mysql Docker tag to v9.5
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-09 14:02:54 +00:00
alazyreader 87472f6674 Merge pull request 'Update module tailscale.com to v1.92.1' (#88) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #88
2025-12-08 16:10:26 +00:00
renovate 53dc114d19 Update module tailscale.com to v1.92.1
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-08 15:05:32 +00:00
alazyreader edd3d71957 Merge pull request 'Update module golang.org/x/sync to v0.19.0' (#89) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #89
2025-12-08 14:59:31 +00:00
renovate 1b064134f3 Update module golang.org/x/sync to v0.19.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-08 13:02:47 +00:00
alazyreader 8e997b769d Merge pull request 'Update module tailscale.com to v1.92.0' (#87) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #87
2025-11-28 20:58:44 +00:00
renovate aee8fedf14 Update module tailscale.com to v1.92.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-26 22:04:53 +00:00
alazyreader a5e8c46fd9 Merge pull request 'Update module tailscale.com to v1.90.9' (#86) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #86
2025-11-26 21:51:35 +00:00
renovate 2cf2d44e57 Update module tailscale.com to v1.90.9
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-25 17:05:21 +00:00
alazyreader f5ce0a4dca Merge pull request 'Update module tailscale.com to v1.90.8' (#85) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #85
2025-11-21 18:06:32 +00:00
renovate cbd1d36516 Update module tailscale.com to v1.90.8
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-18 21:03:58 +00:00
alazyreader 439e77e095 Merge pull request 'Update module golang.org/x/sync to v0.18.0' (#84) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #84
2025-11-17 03:38:07 +00:00
renovate ab16d26cc3 Update module golang.org/x/sync to v0.18.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-08 13:02:49 +00:00
alazyreader 45211f4b89 Merge pull request 'Update module tailscale.com to v1.90.6' (#83) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #83
2025-11-01 20:11:49 +00:00
renovate 069a4d178c Update module tailscale.com to v1.90.6
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-31 22:02:51 +00:00
alazyreader 0196e63579 Merge pull request 'Update module tailscale.com to v1.90.2' (#81) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #81
2025-10-27 15:36:26 +00:00
renovate 60452be32d Update module tailscale.com to v1.90.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-24 18:03:28 +00:00
alazyreader 78b2afd4fb Merge pull request 'Update module tailscale.com to v1.88.4' (#80) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #80
2025-10-15 16:33:10 +00:00
renovate 88fcada19d Update module tailscale.com to v1.88.4
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-14 19:02:17 +00:00
alazyreader 16e270371e Merge pull request 'Update module tailscale.com to v1.88.3' (#79) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #79
2025-09-29 13:49:01 +00:00
renovate cc0174ed24 Update module tailscale.com to v1.88.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-09-25 14:02:41 +00:00
alazyreader 8763bda3f4 upgrade sync
ci/woodpecker/push/woodpecker Pipeline was successful
2025-09-19 21:03:52 -04:00
alazyreader 212fa8be4d Merge pull request 'Update module tailscale.com to v1.88.2' (#77) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #77
2025-09-19 14:13:27 +00:00
alazyreader 249e98d060 upgrade go in test
ci/woodpecker/push/woodpecker Pipeline was successful
2025-09-19 10:05:31 -04:00
alazyreader 76964a6a00 update go
ci/woodpecker/push/woodpecker Pipeline failed
2025-09-19 10:04:44 -04:00
renovate 6978b3dc47 Update module tailscale.com to v1.88.2
ci/woodpecker/push/woodpecker Pipeline failed
2025-09-17 18:02:27 +00:00
alazyreader b8865b41dc Merge pull request 'Update mysql Docker tag to v9.4' (#76) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #76
2025-09-14 01:17:25 +00:00
renovate 23fd21dbf5 Update mysql Docker tag to v9.4
ci/woodpecker/push/woodpecker Pipeline was successful
2025-09-12 14:02:04 +00:00
alazyreader 2fcc42597d Merge pull request 'Update module tailscale.com to v1.84.3' (#74) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #74
2025-06-29 18:52:44 +00:00
renovate f3f8995d53 Update module tailscale.com to v1.84.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-26 17:02:02 +00:00
alazyreader 6c9320efae Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.9.3' (#73) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #73
2025-06-13 20:14:18 +00:00
renovate c9131bded8 Update module github.com/go-sql-driver/mysql to v1.9.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-13 07:01:34 +00:00
alazyreader e1fe23bcba Merge pull request 'Update module tailscale.com to v1.84.2' (#72) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #72
2025-06-10 14:07:48 +00:00
renovate d3febf1f6c Update module tailscale.com to v1.84.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-09 22:02:14 +00:00
alazyreader 0fe9679891 Merge pull request 'Update module golang.org/x/sync to v0.15.0' (#71) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #71
2025-06-09 01:24:07 +00:00
renovate 03c835139f Update module golang.org/x/sync to v0.15.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-06 19:01:59 +00:00
alazyreader af42df691b Merge pull request 'remove tui, unused' (#70) from remove-tui into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #70
2025-06-06 18:46:59 +00:00
alazyreader 95b4fc1802 remove tui, unused
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-04 22:04:51 -04:00
alazyreader 3874b2cc9d Merge pull request 'Update module tailscale.com to v1.84.1' (#69) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #69
2025-06-05 01:58:13 +00:00
renovate 049e55c9c9 Update module tailscale.com to v1.84.1
ci/woodpecker/push/woodpecker Pipeline was successful
2025-05-29 18:02:23 +00:00
alazyreader a370265e2e Merge pull request 'Update mysql Docker tag to v9.3' (#44) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #44
2025-05-26 04:13:26 +00:00
alazyreader 535dd86aba Merge pull request 'Update module golang.org/x/sync to v0.14.0' (#67) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #67
2025-05-23 02:16:02 +00:00
renovate b0896539c8 Update module golang.org/x/sync to v0.14.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-05-23 01:06:13 +00:00
alazyreader 28d9c9b659 Merge pull request 'Update module tailscale.com to v1.84.0' (#68) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #68
2025-05-23 00:55:32 +00:00
renovate 6f9e1ec589 Update module tailscale.com to v1.84.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-05-21 20:02:45 +00:00
alazyreader 2adbe3004f Merge pull request 'Update module tailscale.com to v1.82.5' (#66) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #66
2025-05-14 02:10:47 +00:00
renovate da9fbaf347 Update module tailscale.com to v1.82.5
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-17 20:02:47 +00:00
renovate 5bca510e83 Update mysql Docker tag to v9.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-15 04:01:58 +00:00
alazyreader e76c63ccdc Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.9.2' (#65) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #65
2025-04-07 15:21:09 +00:00
renovate 288c345a22 Update module github.com/go-sql-driver/mysql to v1.9.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-07 12:02:07 +00:00
alazyreader 151265cc0e Merge pull request 'Update module golang.org/x/sync to v0.13.0' (#64) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #64
2025-04-06 16:59:49 +00:00
renovate 91cd6a1810 Update module golang.org/x/sync to v0.13.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-05 14:02:09 +00:00
alazyreader 64b767d0b5 Merge pull request 'Update module tailscale.com to v1.82.0' (#63) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #63
2025-03-30 20:11:08 +00:00
alazyreader 94de300e16 1.24 upgrade
ci/woodpecker/push/woodpecker Pipeline failed
2025-03-30 15:36:04 -04:00
renovate 813f8a43b2 Update module tailscale.com to v1.82.0
ci/woodpecker/push/woodpecker Pipeline failed
2025-03-26 21:03:02 +00:00
alazyreader d219f2b142 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.9.1' (#62) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #62
2025-03-25 01:44:01 +00:00
renovate bbc4a5fecf Update module github.com/go-sql-driver/mysql to v1.9.1
ci/woodpecker/push/woodpecker Pipeline was successful
2025-03-21 03:02:13 +00:00
alazyreader 20de1eccbb Merge pull request 'Update module golang.org/x/sync to v0.12.0' (#61) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #61
2025-03-19 20:16:20 +00:00
renovate 7076c40e63 Update module golang.org/x/sync to v0.12.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-03-05 14:03:29 +00:00
alazyreader 7fafd997ca Merge pull request 'Update module tailscale.com to v1.80.3' (#60) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #60
2025-03-04 15:57:19 +00:00
renovate 007c4a5954 Update module tailscale.com to v1.80.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-03-04 00:02:45 +00:00
alazyreader c39837fa0a Merge pull request 'Update module tailscale.com to v1.80.2' (#57) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #57
2025-02-19 03:02:48 +00:00
alazyreader 956051a8b5 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.9.0' (#59) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #59
2025-02-18 14:46:15 +00:00
renovate 81bdb3d4d8 Update module github.com/go-sql-driver/mysql to v1.9.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-18 04:03:02 +00:00
renovate 33354dc21a Update module tailscale.com to v1.80.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-12 19:03:58 +00:00
alazyreader 37682d66c4 Merge pull request 'Update module tailscale.com to v1.80.1' (#56) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #56
2025-02-09 03:17:38 +00:00
renovate bd6831233e Update module tailscale.com to v1.80.1
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-06 19:03:20 +00:00
alazyreader dcf5508576 Merge pull request 'Update module golang.org/x/sync to v0.11.0' (#55) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #55
2025-02-06 14:28:14 +00:00
renovate c996dcaef2 Update module golang.org/x/sync to v0.11.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-04 15:02:25 +00:00
alazyreader 0e09e47694 Merge pull request 'Update module github.com/gdamore/tcell/v2 to v2.8.1' (#53) from renovate/github.com-gdamore-tcell-v2-2.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #53
2025-02-04 03:46:05 +00:00
alazyreader 3d4d10f11c implement missing mock methods
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-03 22:41:59 -05:00
renovate ae0eb5f001 Update module github.com/gdamore/tcell/v2 to v2.8.1
ci/woodpecker/push/woodpecker Pipeline failed
2025-02-04 03:03:05 +00:00
alazyreader d36a89f61c Merge pull request 'Update module tailscale.com to v1.80.0' (#54) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #54
2025-02-04 02:23:21 +00:00
renovate 441809573e Update module tailscale.com to v1.80.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-30 22:02:59 +00:00
alazyreader 671bffdb2f update secrets syntax
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-05 19:08:51 +00:00
alazyreader a3413934b8 Merge pull request 'Update module golang.org/x/sync to v0.10.0' (#50) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/manual/woodpecker Pipeline failed
Reviewed-on: #50
2024-12-18 14:03:03 +00:00
renovate 5fb1786b5a Update module golang.org/x/sync to v0.10.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-15 04:08:01 +00:00
alazyreader 8293b7b384 Merge pull request 'Update module tailscale.com to v1.78.3' (#52) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #52
2024-12-15 03:45:04 +00:00
renovate db543392d9 Update module tailscale.com to v1.78.3
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-11 21:03:17 +00:00
alazyreader 405063d20b Merge pull request 'Update module tailscale.com to v1.78.1' (#51) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #51
2024-12-07 21:34:22 +00:00
renovate 030a403641 Update module tailscale.com to v1.78.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-06 01:03:08 +00:00
alazyreader 935956c7bf Merge pull request 'Update module tailscale.com to v1.76.6' (#47) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #47
2024-11-10 19:21:46 +00:00
renovate d9d57c4b2b Update module tailscale.com to v1.76.6
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-09 21:02:21 +00:00
alazyreader 155f5a967f Merge pull request 'Update module golang.org/x/sync to v0.9.0' (#49) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #49
2024-11-09 20:30:51 +00:00
renovate de3a121b3d Update module golang.org/x/sync to v0.9.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-07 22:02:19 +00:00
alazyreader 7175484edb Merge pull request 'Update module tailscale.com to v1.76.3' (#46) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #46
2024-10-29 15:27:49 +00:00
renovate 9a1974a538 Update module tailscale.com to v1.76.3
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-22 01:02:47 +00:00
alazyreader 757112837d Merge pull request 'Update module tailscale.com to v1.76.1' (#45) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #45
2024-10-22 00:37:13 +00:00
renovate 990d18bc64 Update module tailscale.com to v1.76.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-17 19:02:48 +00:00
alazyreader cd9c4e72ac Merge pull request 'Update module tailscale.com to v1.76.0' (#43) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #43
2024-10-13 18:23:56 +00:00
renovate 2ee8278652 Update module tailscale.com to v1.76.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-10 19:02:34 +00:00
alazyreader d4f021ff89 use proper host
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-23 14:06:38 +00:00
alazyreader 89291c1c0a Merge pull request 'Update module tailscale.com to v1.74.1' (#41) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #41
2024-09-21 18:06:26 +00:00
renovate bd7cf08fcb Update module tailscale.com to v1.74.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-18 20:02:23 +00:00
alazyreader dc556e210e Merge pull request 'Update module tailscale.com to v1.74.0' (#40) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #40
2024-09-14 17:16:41 +00:00
renovate 734f2d094f Update module tailscale.com to v1.74.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-12 21:02:17 +00:00
alazyreader aca9b1f672 Merge pull request 'Update module tailscale.com to v1.72.1' (#39) from renovate/tailscale.com-1.x into master
Reviewed-on: #39
2024-09-03 12:26:35 +00:00
renovate b5f04fb3dc Update module tailscale.com to v1.72.1 2024-09-02 18:02:49 +00:00
alazyreader eef2505285 Merge pull request 'Update golang Docker tag to v1.23' (#38) from renovate/golang-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #38
2024-08-20 00:49:33 +00:00
renovate a6887df550 Update golang Docker tag to v1.23
ci/woodpecker/push/woodpecker Pipeline was successful
2024-08-13 21:01:35 +00:00
alazyreader c5b4464a59 Merge pull request 'Update module golang.org/x/sync to v0.8.0' (#37) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #37
2024-08-08 12:21:46 +00:00
renovate b641ac0ca6 Update module golang.org/x/sync to v0.8.0 2024-08-04 16:04:37 +00:00
alazyreader 29434549ce Merge pull request 'Update mysql Docker tag to v9' (#36) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #36
2024-07-12 13:49:12 +00:00
alazyreader c086a0ccb1 upgrade tsnet
ci/woodpecker/push/woodpecker Pipeline was successful
2024-07-08 08:46:16 -04:00
renovate 7bb73b8023 Update mysql Docker tag to v9
ci/woodpecker/push/woodpecker Pipeline was successful
2024-07-02 22:01:32 +00:00
alazyreader cdeb5e2d84 Merge pull request 'Update mysql Docker tag to v8.4' (#20) from renovate/mysql-8.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #20
2024-06-09 14:47:50 +00:00
alazyreader 551f1ef203 Merge pull request 'Update module tailscale.com to v1.66.4' (#35) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline is running
Reviewed-on: #35
2024-06-09 14:47:37 +00:00
renovate 5ef60f70f1 Update module tailscale.com to v1.66.4
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-21 01:02:06 +00:00
renovate f5d8ad7c8c Update mysql Docker tag to v8.4
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-01 01:01:28 +00:00
alazyreader b1708ec1a8 Merge pull request 'Update module golang.org/x/sync to v0.7.0' (#32) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #32
2024-04-22 00:47:04 +00:00
renovate 94a9a0b77e Update module golang.org/x/sync to v0.7.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-04-18 18:01:37 +00:00
alazyreader 0ab60f1297 Merge pull request 'Update module tailscale.com to v1.64.2' (#34) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline is running
Reviewed-on: #34
2024-04-18 17:35:51 +00:00
renovate b68ac7f643 Update module tailscale.com to v1.64.2
ci/woodpecker/push/woodpecker Pipeline was successful
2024-04-17 15:01:47 +00:00
alazyreader 5f4da44bf0 clean up go mod file 2024-04-13 13:45:35 -04:00
alazyreader 0a337e42dd Merge pull request 'Update module tailscale.com to v1.64.0' (#33) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #33
2024-04-13 13:59:38 +00:00
renovate 165a913828 Update module tailscale.com to v1.64.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-04-11 20:02:11 +00:00
alazyreader aec3b2dff4 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.8.1' (#30) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #30
2024-03-29 19:00:45 +00:00
alazyreader cf8b07725b Merge pull request 'Update module tailscale.com to v1.62.1' (#31) from renovate/tailscale.com-1.x into master
Reviewed-on: #31
2024-03-29 18:59:40 +00:00
renovate 467d8202e2 Update module tailscale.com to v1.62.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-26 21:01:40 +00:00
renovate 2b6024e229 Update module github.com/go-sql-driver/mysql to v1.8.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-26 15:01:31 +00:00
alazyreader 62d03849a0 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.8.0' (#28) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #28
2024-03-18 00:15:25 +00:00
alazyreader 7610034240 Merge pull request 'Update module tailscale.com to v1.62.0' (#29) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #29
2024-03-18 00:12:41 +00:00
renovate 7a6f5740e0 Update module tailscale.com to v1.62.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-13 16:01:38 +00:00
renovate 09478cbd5b Update module github.com/go-sql-driver/mysql to v1.8.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-09 08:01:26 +00:00
alazyreader 609d6cf166 Merge pull request 'Update module tailscale.com to v1.60.1' (#25) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #25
2024-03-04 01:43:16 +00:00
alazyreader 55b6c0689d Merge pull request 'Update module github.com/gdamore/tcell/v2 to v2.7.4' (#26) from renovate/github.com-gdamore-tcell-v2-2.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #26
2024-03-04 01:40:58 +00:00
renovate 363dc85eb4 Update module github.com/gdamore/tcell/v2 to v2.7.4
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-04 00:01:29 +00:00
renovate c9b9805cee Update module tailscale.com to v1.60.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-29 22:01:32 +00:00
alazyreader bf3c3d1dd7 bump build go
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-25 01:42:16 +00:00
alazyreader e3e8d68c95 upgrade go
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-25 01:39:41 +00:00
alazyreader 960b0b8766 use steps
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-25 01:39:13 +00:00
alazyreader f7b2e32651 Merge pull request 'Update module github.com/gdamore/tcell/v2 to v2.7.1' (#24) from renovate/github.com-gdamore-tcell-v2-2.x into master
Reviewed-on: #24
2024-02-21 01:23:44 +00:00
renovate 1554b151cb Update module github.com/gdamore/tcell/v2 to v2.7.1 2024-02-20 16:01:27 +00:00
alazyreader a191e41453 Merge pull request 'Update module tailscale.com to v1.60.0' (#23) from renovate/tailscale.com-1.x into master
Reviewed-on: #23
2024-02-20 15:22:19 +00:00
renovate 0848406a85 Update module tailscale.com to v1.60.0 2024-02-15 21:01:58 +00:00
alazyreader 0ce38e5453 Merge pull request 'Update module tailscale.com to v1.58.2' (#21) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #21
2024-01-27 18:04:29 +00:00
renovate bd1bf93ea0 Update module tailscale.com to v1.58.2
ci/woodpecker/push/woodpecker Pipeline was successful
2024-01-23 23:01:31 +00:00
alazyreader c26e9513ad Merge pull request 'Update module tailscale.com to v1.58.0' (#19) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #19
2024-01-20 01:55:46 +00:00
renovate cb53a35de6 Update module tailscale.com to v1.58.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-01-19 02:02:36 +00:00
alazyreader d69d416c49 Merge pull request 'Update module golang.org/x/sync to v0.6.0' (#18) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #18
2024-01-07 04:13:01 +00:00
renovate caba03e58a Update module golang.org/x/sync to v0.6.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-01-07 04:01:32 +00:00
alazyreader 315fb4e9d2 management server listener (#17)
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #17
Co-authored-by: David Ashby <delta.mu.alpha@gmail.com>
Co-committed-by: David Ashby <delta.mu.alpha@gmail.com>
2024-01-06 21:38:13 +00:00
alazyreader dc7218131d Merge pull request 'Update module github.com/gdamore/tcell/v2 to v2.7.0' (#16) from renovate/github.com-gdamore-tcell-v2-2.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #16
2023-12-24 20:06:45 +00:00
alazyreader 878635450c add methods to mockscreen
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-24 15:05:11 -05:00
renovate 138a4a62c1 Update module github.com/gdamore/tcell/v2 to v2.7.0
ci/woodpecker/push/woodpecker Pipeline failed
2023-12-14 22:01:33 +00:00
26 changed files with 1084 additions and 1729 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
/server
/manager
*.properties
.DS_Store
/vendor
.recordsCache
.recordsCache
.config
+25 -6
View File
@@ -1,19 +1,38 @@
pipeline:
steps:
test:
image: golang:1.21
image: golang:1.27
commands:
- go test ./...
volumes:
- go_build_cache:/root/.cache/go-build
- go_mod_cache:/go/pkg/mod
build:
image: golang:1.27
environment:
CGO_ENABLED: 0
GOOS: linux
commands:
- go build -o server ./cmd/serve
volumes:
- go_build_cache:/root/.cache/go-build
- go_mod_cache:/go/pkg/mod
container:
image: docker
commands:
- apk add curl
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD registry.yetaga.in
- docker login -u docker -p $DOCKER_PASSWORD registry.yetaga.in
- docker build -t registry.yetaga.in/library:latest .
- docker push registry.yetaga.in/library:latest
- 'curl http://172.17.0.1:4000/api/fetch -H "Authorization: Bearer $COMPOSE_TOKEN"'
- 'curl http://172.17.0.1:4000/api/update -H "Authorization: Bearer $COMPOSE_TOKEN"'
secrets: [docker_username, docker_password, compose_token]
- 'curl http://100.113.98.36:4000/api/fetch -H "Authorization: Bearer $COMPOSE_TOKEN"'
- 'curl http://100.113.98.36:4000/api/update -H "Authorization: Bearer $COMPOSE_TOKEN"'
environment:
DOCKER_PASSWORD:
from_secret: docker_password
COMPOSE_TOKEN:
from_secret: compose_token
when:
branch: "master"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- go_build_cache:/root/.cache/go-build
- go_mod_cache:/go/pkg/mod
+2 -8
View File
@@ -1,9 +1,3 @@
FROM golang:1.21
WORKDIR /src
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/serve
FROM scratch
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=0 /src/server ./
FROM scratch
COPY server ./
CMD ["/server"]
+5 -12
View File
@@ -1,34 +1,27 @@
.PHONY: up down run-server run-manager test
.PHONY: up down run test
GOFILES=$(shell find . -name '*.go' -o -name 'go.*')
STATICFILES=$(shell find . -name '*.js' -o -name '*.css' -o -name '*.html')
SQLFILES=$(shell find . -name '*.sql')
ifneq (,$(wildcard ./local.properties))
include local.properties
export
endif
build: server manager
build: server
run-server: build
run: build
./server
run-manager: build
./manager
server: $(GOFILES) $(STATICFILES)
go build -o server ./cmd/serve
manager: $(GOFILES) $(SQLFILES)
go build -o manager ./cmd/manage
test:
go test ./... -cover
# dev dependencies
up:
docker compose up -d
docker-compose up -d
down:
docker compose down
docker-compose down
-113
View File
@@ -1,113 +0,0 @@
package main
import (
"git.yetaga.in/alazyreader/library/media"
"github.com/gdamore/tcell/v2"
)
// error message
type EventError struct {
tcell.EventTime
err error
}
func NewEventError(err error) *EventError {
e := &EventError{err: err}
e.SetEventNow()
return e
}
// save change to book
type EventBookUpdate struct {
tcell.EventTime
book *media.Book
}
func NewEventBookUpdate(b *media.Book) *EventBookUpdate {
e := &EventBookUpdate{book: b}
e.SetEventNow()
return e
}
func (e *EventBookUpdate) Book() *media.Book {
return e.book
}
// open new book in display
type EventLoadBook struct {
tcell.EventTime
ID int
}
func NewEventLoadBook(id int) *EventLoadBook {
e := &EventLoadBook{ID: id}
e.SetEventNow()
return e
}
// open new book in display
type EventEnterBook struct {
tcell.EventTime
}
func NewEventEnterBook() *EventEnterBook {
e := &EventEnterBook{}
e.SetEventNow()
return e
}
// switch back to menu control
type EventExitBook struct {
tcell.EventTime
}
func NewEventExitBook() *EventExitBook {
e := &EventExitBook{}
e.SetEventNow()
return e
}
// open import window
type EventOpenImport struct {
tcell.EventTime
}
func NewEventOpenImport() *EventOpenImport {
e := &EventOpenImport{}
e.SetEventNow()
return e
}
// attempt to import given filename.csv
type EventAttemptImport struct {
tcell.EventTime
filename string
}
func NewEventAttemptImport(f string) *EventAttemptImport {
e := &EventAttemptImport{filename: f}
e.SetEventNow()
return e
}
// close import window
type EventCloseImport struct {
tcell.EventTime
}
func NewEventCloseImport() *EventCloseImport {
e := &EventCloseImport{}
e.SetEventNow()
return e
}
// quit
type EventQuit struct {
tcell.EventTime
}
func NewEventQuit() *EventQuit {
e := &EventQuit{}
e.SetEventNow()
return e
}
-336
View File
@@ -1,336 +0,0 @@
package main
import (
"context"
"fmt"
"log"
"os"
"runtime/debug"
"strings"
"sync"
"git.yetaga.in/alazyreader/library/config"
"git.yetaga.in/alazyreader/library/database"
"git.yetaga.in/alazyreader/library/importer"
"git.yetaga.in/alazyreader/library/media"
"git.yetaga.in/alazyreader/library/ui"
"github.com/gdamore/tcell/v2"
"github.com/kelseyhightower/envconfig"
)
// State holds the UI state keys=>value map and manages access to the map with a mutex
type State struct {
m sync.Mutex
stateMap map[string]interface{}
}
// key, present
func (s *State) Get(key string) interface{} {
s.m.Lock()
defer s.m.Unlock()
if s.stateMap == nil {
s.stateMap = make(map[string]interface{})
}
k, ok := s.stateMap[key]
if !ok {
return nil
}
return k
}
// key, value
func (s *State) Set(key string, value interface{}) {
s.m.Lock()
defer s.m.Unlock()
if s.stateMap == nil {
s.stateMap = make(map[string]interface{})
}
s.stateMap[key] = value
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
// UI states
const (
IN_MENU = iota
IN_BOOK
IN_IMPORT
)
func main() {
var c config.Config
err := envconfig.Process("library", &c)
if err != nil {
log.Fatalln(err)
}
// create state
state := State{}
// set up DB connection
if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" {
if c.DBPass != "" { // obscure password
c.DBPass = c.DBPass[0:max(3, len(c.DBPass))] + strings.Repeat("*", max(0, len(c.DBPass)-3))
}
log.Fatalf("vars: %+v", c)
}
lib, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName)
if err != nil {
log.Fatalln(err)
}
err = lib.PrepareDatabase(context.Background())
if err != nil {
log.Fatalln(err)
}
_, _, err = lib.RunMigrations(context.Background())
if err != nil {
log.Fatalln(err)
}
books, err := lib.GetAllBooks(context.Background())
if err != nil {
log.Fatalln(err)
}
state.Set("library", books)
screen, err := tcell.NewScreen()
if err != nil {
log.Fatalln(err)
}
err = screen.Init()
if err != nil {
log.Fatalln(err)
}
// cleanup our screen and log if we panic and crash out somewhere
defer func() {
if r := recover(); r != nil {
if screen != nil {
screen.Fini()
}
fmt.Println("fatal panic;", r)
if c.Debug {
fmt.Println("stacktrace: \n" + string(debug.Stack()))
}
return
}
}()
// book list and options menu (left column)
l := ui.NewList(Titles(state.Get("library").([]media.Book)), 0)
menu := ui.NewBox(
"library",
[]string{"˄˅ select", "⏎ edit", "(n)ew", "(i)mport", "(q)uit"},
ui.Contents{{
Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: -2, Right: -2},
Container: l,
}},
ui.StyleActive,
false,
)
activeBookDetails := ui.NewBookDetails(&media.Book{})
// book display (right column)
activeBook := ui.NewBox(
"book",
[]string{"˄˅ select", "⏎ edit", "(esc) close"},
ui.Contents{{
Offsets: ui.Offsets{Top: 1, Left: 2, Bottom: 0, Right: 0},
Container: activeBookDetails,
}},
ui.StyleInactive,
false,
)
// parent container
container := ui.NewContainer(
ui.Contents{
{Container: menu, Offsets: ui.Offsets{Percent: 1}},
{Container: activeBook, Offsets: ui.Offsets{Percent: 2}},
},
ui.LayoutHorizontalPercent,
)
// import pop-up
wd, _ := os.Getwd()
fileSelector := ui.NewEditableTextLine(wd)
fileSelector.ResetCursor(false)
fileSelector.SetStyle(ui.StyleActive.Underline(true))
popup := ui.NewBox(
"import csv file",
[]string{"⏎ submit", "(esc)close"},
ui.Contents{
{Container: fileSelector, Offsets: ui.Offsets{Top: 2, Left: 2}},
},
ui.StyleActive,
false,
)
popup.SetVisible(false)
// error pop-up
errorMessage := ui.NewEditableTextLine("")
errorPopup := ui.NewBox(
"error",
[]string{"⏎ close"},
ui.Contents{
{Container: errorMessage, Offsets: ui.Offsets{Top: 1, Left: 1}},
},
ui.StyleActive.Bold(true).Foreground(tcell.ColorRed),
false,
)
errorPopup.SetVisible(false)
// init
screen.Clear()
w, h := screen.Size()
container.SetSize(0, 0, h, w)
container.Draw(screen)
screen.Sync()
// init UI state
state.Set("ui_state", IN_MENU)
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
// UI loop
for {
e := screen.PollEvent()
switch v := e.(type) {
case *tcell.EventError:
fmt.Fprintf(os.Stderr, "%v", v)
screen.Beep()
case *tcell.EventKey: // input handling
curr := state.Get("ui_state").(int)
if curr == IN_MENU {
if v.Key() == tcell.KeyUp && l.Selected() > 0 {
l.SetSelected(l.Selected() - 1)
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
}
if v.Key() == tcell.KeyDown && l.Selected() < len(l.ListMembers())-1 {
l.SetSelected(l.Selected() + 1)
screen.PostEvent(NewEventLoadBook(l.SelectedID()))
}
if v.Key() == tcell.KeyEnter {
screen.PostEvent(NewEventEnterBook())
}
if v.Rune() == 'i' {
screen.PostEvent(NewEventOpenImport())
}
if v.Rune() == 'q' {
screen.PostEvent(NewEventQuit())
}
} else if curr == IN_BOOK {
if v.Key() == tcell.KeyEsc {
screen.PostEvent(NewEventExitBook())
}
} else if curr == IN_IMPORT {
if v.Key() == tcell.KeyEsc {
fileSelector.SetText(wd)
fileSelector.ResetCursor(false)
screen.PostEvent(NewEventCloseImport())
}
if v.Key() == tcell.KeyBackspace || v.Key() == tcell.KeyBackspace2 {
fileSelector.DeleteAtCursor()
} else if v.Key() == tcell.KeyRight {
fileSelector.MoveCursor(1)
} else if v.Key() == tcell.KeyLeft {
fileSelector.MoveCursor(-1)
} else if v.Key() == tcell.KeyEnter {
screen.PostEvent(NewEventAttemptImport(fileSelector.Text()))
} else if v.Rune() != 0 {
fileSelector.InsertAtCursor(v.Rune())
}
}
case *tcell.EventResize: // screen redraw
w, h := screen.Size()
container.SetSize(0, 0, h, w)
case *EventBookUpdate:
// TK
case *EventEnterBook:
activeBook.SetStyle(ui.StyleActive)
menu.SetStyle(ui.StyleInactive)
state.Set("ui_state", IN_BOOK)
case *EventExitBook:
state.Set("ui_state", IN_MENU)
activeBook.SetStyle(ui.StyleInactive)
menu.SetStyle(ui.StyleActive)
case *EventLoadBook:
activeBookDetails.SetBook(GetBookByID(v.ID, books))
case *EventOpenImport:
state.Set("ui_state", IN_IMPORT)
menu.SetStyle(ui.StyleInactive)
popup.SetVisible(true)
popup.SetSize(6, 3, 5, 80)
case *EventAttemptImport:
// this will block other events, but it shouldn't take too long...
f, err := os.Open(v.filename)
if err != nil {
screen.PostEvent(NewEventError(err))
continue
}
books, err := importer.CSVToBooks(f)
if err != nil {
screen.PostEvent(NewEventError(err))
continue
}
for b := range books {
err = lib.AddBook(context.Background(), &books[b])
if err != nil {
screen.PostEvent(NewEventError(err))
}
}
screen.PostEvent(NewEventCloseImport())
allbooks, err := lib.GetAllBooks(context.Background())
if err != nil {
screen.PostEvent(NewEventError(err))
}
state.Set("library", allbooks)
state.Set("ui_state", IN_MENU)
case *EventCloseImport:
state.Set("ui_state", IN_MENU)
screen.HideCursor()
menu.SetStyle(ui.StyleActive)
popup.SetVisible(false)
case *EventError:
errorMessage.SetText(v.err.Error())
errorPopup.SetVisible(true)
case *EventQuit:
screen.Fini()
fmt.Printf("Thank you for playing Wing Commander!\n\n")
return
case *tcell.EventInterrupt:
case *tcell.EventMouse:
case *tcell.EventTime:
default:
}
// repaint
l.SetMembers(Titles(state.Get("library").([]media.Book)))
container.Draw(screen)
popup.Draw(screen)
errorPopup.Draw(screen)
screen.Show()
}
}
func Titles(lb []media.Book) []ui.ListKeyValue {
r := []ui.ListKeyValue{}
for i := range lb {
r = append(r, ui.ListKeyValue{
Key: lb[i].ID,
Value: lb[i].Title,
})
}
return r
}
func GetBookByID(id int, lb []media.Book) *media.Book {
for i := range lb {
if lb[i].ID == id {
return &lb[i]
}
}
return &media.Book{}
}
+179
View File
@@ -0,0 +1,179 @@
package main
import (
"encoding/json"
"fmt"
"io"
"io/fs"
"log"
"net/http"
"git.yetaga.in/alazyreader/library/media"
"tailscale.com/client/tailscale"
)
type Router struct {
static fs.FS
lib Library
rcol RecordCollection
query Query
ts *tailscale.LocalClient
isAdmin bool
}
type path map[string]func()
func (h path) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if f, ok := h[r.Method]; ok {
f()
return
}
writeJSONerror(w, "method not supported", http.StatusMethodNotAllowed)
}
func writeJSONerror(w http.ResponseWriter, err string, status int) {
log.Println(err)
writeJSON(w, struct{ Status, Reason string }{Status: "error", Reason: err}, status)
}
func writeJSON(w http.ResponseWriter, b any, status int) {
bytes, err := json.Marshal(b)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
w.Write(bytes)
w.Write([]byte("\n"))
}
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/mode":
path{
http.MethodGet: func() {
writeJSON(w, struct{ Admin bool }{Admin: router.isAdmin}, http.StatusOK)
},
}.ServeHTTP(w, r)
case "/api/whoami":
if !router.isAdmin {
http.NotFoundHandler().ServeHTTP(w, r)
return
}
path{
http.MethodGet: func() { getWhoAmI(router.ts, w, r) },
}.ServeHTTP(w, r)
case "/api/records":
path{
http.MethodGet: func() { getRecords(router.rcol, w, r) },
}.ServeHTTP(w, r)
case "/api/books":
p := path{
http.MethodGet: func() { getBooks(router.lib, w, r) },
}
if router.isAdmin {
p[http.MethodPost] = func() { addBook(router.lib, w, r) }
p[http.MethodDelete] = func() { deleteBook(router.lib, w, r) }
}
p.ServeHTTP(w, r)
case "/api/query":
if !router.isAdmin {
http.NotFoundHandler().ServeHTTP(w, r)
return
}
path{
http.MethodPost: func() { lookupBook(router.query, w, r) },
}.ServeHTTP(w, r)
default:
static(router.static).ServeHTTP(w, r)
}
}
func getBooks(l Library, w http.ResponseWriter, r *http.Request) {
books, err := l.GetAllBooks(r.Context())
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, books, http.StatusOK)
}
func addBook(l Library, w http.ResponseWriter, r *http.Request) {
book, err := ReadBody[media.Book](r.Body)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusBadRequest)
return
}
if err = l.AddBook(r.Context(), book); err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusAccepted)
}
func deleteBook(l Library, w http.ResponseWriter, r *http.Request) {
book, err := ReadBody[media.Book](r.Body)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusBadRequest)
return
}
if err = l.DeleteBook(r.Context(), book); err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusAccepted)
}
func getRecords(l RecordCollection, w http.ResponseWriter, r *http.Request) {
records, err := l.GetAllRecords(r.Context())
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, records, http.StatusOK)
}
func getWhoAmI(ts *tailscale.LocalClient, w http.ResponseWriter, r *http.Request) {
whois, err := ts.WhoIs(r.Context(), r.RemoteAddr)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, whois.UserProfile, http.StatusOK)
}
func lookupBook(query Query, w http.ResponseWriter, r *http.Request) {
req, err := ReadBody[media.Book](r.Body)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusBadRequest)
return
}
book, err := query.GetByISBN(req.ISBN13)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, book, http.StatusOK)
}
func static(f fs.FS) http.Handler {
return http.FileServer(http.FS(f))
}
func ReadBody[T any](r io.ReadCloser) (*T, error) {
t := new(T)
if r == nil {
return t, fmt.Errorf("no body provided")
}
defer r.Close()
b, err := io.ReadAll(r)
if err != nil {
return t, fmt.Errorf("error reading body: %w", err)
}
err = json.Unmarshal(b, t)
if err != nil {
return t, fmt.Errorf("error reading body: %w", err)
}
return t, nil
}
+120 -92
View File
@@ -2,10 +2,12 @@ package main
import (
"context"
"encoding/json"
"io/fs"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"strings"
"time"
@@ -13,100 +15,79 @@ import (
"git.yetaga.in/alazyreader/library/database"
"git.yetaga.in/alazyreader/library/frontend"
"git.yetaga.in/alazyreader/library/media"
"git.yetaga.in/alazyreader/library/query"
"github.com/kelseyhightower/envconfig"
_ "golang.org/x/crypto/x509roots/fallback"
"golang.org/x/sync/errgroup"
"tailscale.com/tsnet"
"tailscale.com/util/must"
)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func obscureStr(in string, l int) string {
return in[0:max(l, len(in))] + strings.Repeat("*", max(0, len(in)-l))
}
type Library interface {
GetAllBooks(context.Context) ([]media.Book, error)
AddBook(context.Context, *media.Book) error
DeleteBook(context.Context, *media.Book) error
}
type RecordCollection interface {
GetAllRecords(context.Context) ([]media.Record, error)
}
type Router struct {
static fs.FS
lib Library
rcol RecordCollection
}
func writeJSON(w http.ResponseWriter, b []byte, status int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
w.Write(b)
w.Write([]byte("\n"))
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/api/records" {
RecordsAPIHandler(r.rcol).ServeHTTP(w, req)
return
}
if req.URL.Path == "/api/books" {
BooksAPIHandler(r.lib).ServeHTTP(w, req)
return
}
StaticHandler(r.static).ServeHTTP(w, req)
}
func BooksAPIHandler(l Library) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
books, err := l.GetAllBooks(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
b, err := json.Marshal(books)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, b, http.StatusOK)
})
}
func RecordsAPIHandler(l RecordCollection) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
books, err := l.GetAllRecords(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
b, err := json.Marshal(books)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, b, http.StatusOK)
})
}
func StaticHandler(f fs.FS) http.Handler {
return http.FileServer(http.FS(f))
type Query interface {
GetByISBN(string) (*media.Book, error)
}
func main() {
var c config.Config
err := envconfig.Process("library", &c)
if err != nil {
log.Fatalln(err)
}
f, err := frontend.Root()
if err != nil {
log.Fatalln(err)
must.Do(envconfig.Process("library", &c))
var lib Library
if c.DBType == "memory" {
lib = &database.Memory{}
} else if c.DBType == "sql" {
sqllib, latest, run, err := setupSQL(c)
if err != nil {
log.Fatalf("sql connection err: %v", err)
}
log.Printf("latest migration: %d; migrations run: %d", latest, run)
lib = sqllib
}
discogsCache := must.Get(database.NewDiscogsCache(
c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile,
))
queryProvider := &query.GoogleBooks{}
staticRoot := must.Get(frontend.Root())
servers := make(chan (*http.Server), 3)
errGroup := errgroup.Group{}
errGroup.Go(func() error {
return start(servers)(publicServer(8080, &Router{
static: staticRoot,
lib: lib,
rcol: discogsCache,
isAdmin: false,
}))
})
errGroup.Go(func() error {
return start(servers)(tailscaleListener("library-admin", &Router{
static: staticRoot,
lib: lib,
rcol: discogsCache,
query: queryProvider,
isAdmin: true,
}))
})
errGroup.Go(func() error {
return shutdown(servers)
})
log.Println(errGroup.Wait())
}
func setupSQL(c config.Config) (Library, int, int, error) {
if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" {
if c.DBPass != "" {
c.DBPass = obscureStr(c.DBPass, 3)
@@ -114,30 +95,77 @@ func main() {
if c.DiscogsToken != "" {
c.DiscogsToken = obscureStr(c.DiscogsToken, 3)
}
log.Fatalf("vars: %+v", c)
return nil, 0, 0, fmt.Errorf("invalid config; vars provided: %+v", c)
}
lib, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName)
sql, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName)
if err != nil {
log.Fatalln(err)
return nil, 0, 0, err
}
err = lib.PrepareDatabase(context.Background())
err = sql.PrepareDatabase(context.Background())
if err != nil {
log.Fatalln(err)
return nil, 0, 0, err
}
latest, run, err := lib.RunMigrations(context.Background())
latest, run, err := sql.RunMigrations(context.Background())
if err != nil {
log.Fatalln(err)
return nil, 0, 0, err
}
log.Printf("latest migration: %d; migrations run: %d", latest, run)
discogsCache, err := database.NewDiscogsCache(c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile)
return sql, latest, run, nil
}
func start(servers chan (*http.Server)) func(*http.Server, net.Listener, error) error {
return func(s *http.Server, l net.Listener, err error) error {
if err != nil {
return err
}
servers <- s
return s.Serve(l)
}
}
func shutdown(servers chan (*http.Server)) error {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt)
<-sigint
close(servers)
var err error
for server := range servers {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
if shutdownerr := server.Shutdown(ctx); shutdownerr != nil {
err = shutdownerr
}
cancel()
}
return err
}
func publicServer(port int, handler http.Handler) (*http.Server, net.Listener, error) {
server := &http.Server{Handler: handler}
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return nil, nil, err
}
log.Printf("public server: http://0.0.0.0:%d/", port)
return server, ln, nil
}
func tailscaleListener(hostname string, handler *Router) (*http.Server, net.Listener, error) {
s := &tsnet.Server{
Dir: ".config/" + hostname,
Hostname: hostname,
Logf: func(s string, a ...any) { // silence most tsnet logs
if strings.HasPrefix(s, "To start this tsnet server") {
log.Printf(s, a...)
}
},
}
ln, err := s.Listen("tcp", ":80")
if err != nil {
log.Fatalln(err)
return nil, nil, err
}
r := &Router{
static: f,
lib: lib,
rcol: discogsCache,
handler.ts, err = s.LocalClient()
if err != nil {
return nil, nil, err
}
log.Println("Listening on http://0.0.0.0:8080/")
log.Fatalln(http.ListenAndServe(":8080", r))
log.Printf("management server: http://%s/", hostname)
return &http.Server{Handler: handler}, ln, nil
}
+1
View File
@@ -1,6 +1,7 @@
package config
type Config struct {
DBType string `default:"sql"`
DBUser string
DBPass string
DBHost string
+20 -60
View File
@@ -153,9 +153,8 @@ func (m *MySQL) GetAllBooks(ctx context.Context) ([]media.Book, error) {
}
allBooksQuery := fmt.Sprintf(`SELECT
id, title, authors, sortauthor, isbn10, isbn13, format,
genre, publisher, series, volume, year, signed,
description, notes, coverurl, childrens
id, title, authors, sortauthor, isbn10, isbn13, format, genre, publisher,
series, volume, year, signed, description, notes, coverurl, childrens
FROM %s`, m.tableName)
books := []media.Book{}
@@ -169,12 +168,8 @@ func (m *MySQL) GetAllBooks(ctx context.Context) ([]media.Book, error) {
b := media.Book{}
var authors string
err := rows.Scan(
&b.ID, &b.Title, &authors,
&b.SortAuthor, &b.ISBN10, &b.ISBN13,
&b.Format, &b.Genre, &b.Publisher,
&b.Series, &b.Volume, &b.Year,
&b.Signed, &b.Description, &b.Notes,
&b.CoverURL, &b.Childrens)
&b.ID, &b.Title, &authors, &b.SortAuthor, &b.ISBN10, &b.ISBN13, &b.Format, &b.Genre, &b.Publisher,
&b.Series, &b.Volume, &b.Year, &b.Signed, &b.Description, &b.Notes, &b.CoverURL, &b.Childrens)
if err != nil {
return nil, err
}
@@ -193,25 +188,14 @@ func (m *MySQL) AddBook(ctx context.Context, b *media.Book) error {
res, err := m.connection.ExecContext(ctx, `
INSERT INTO `+m.tableName+`
(title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, series, volume, year, signed, description, notes, coverurl, childrens)
(
title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, series,
volume, year, signed, description, notes, coverurl, childrens
)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
b.Title,
strings.Join(b.Authors, ";"),
b.SortAuthor,
b.ISBN10,
b.ISBN13,
b.Format,
b.Genre,
b.Publisher,
b.Series,
b.Volume,
b.Year,
b.Signed,
b.Description,
b.Notes,
b.CoverURL,
b.Childrens,
b.Title, strings.Join(b.Authors, ";"), b.SortAuthor, b.ISBN10, b.ISBN13, b.Format, b.Genre, b.Publisher, b.Series,
b.Volume, b.Year, b.Signed, b.Description, b.Notes, b.CoverURL, b.Childrens,
)
if err != nil {
return err
@@ -236,41 +220,13 @@ func (m *MySQL) UpdateBook(ctx context.Context, old, new *media.Book) error {
res, err := m.connection.ExecContext(ctx, `
UPDATE `+m.tableName+`
SET id=?
title=?
authors=?
sortauthor=?
isbn10=?
isbn13=?
format=?
genre=?
publisher=?
series=?
volume=?
year=?
signed=?
description=?
notes=?
coverurl=?
childrens=?
SET
title=?, authors=?, sortauthor=?, isbn10=?, isbn13=?, format=?, genre=?, publisher=?,
series=?, volume=?, year=?, signed=?, description=?, notes=?, coverurl=?, childrens=?
WHERE id=?`,
new.Title,
strings.Join(new.Authors, ";"),
new.SortAuthor,
new.ISBN10,
new.ISBN13,
new.Format,
new.Genre,
new.Publisher,
new.Series,
new.Volume,
new.Year,
new.Signed,
new.Description,
new.Notes,
new.CoverURL,
new.Childrens,
old.ID)
new.Title, strings.Join(new.Authors, ";"), new.SortAuthor, new.ISBN10, new.ISBN13, new.Format, new.Genre, new.Publisher,
new.Series, new.Volume, new.Year, new.Signed, new.Description, new.Notes, new.CoverURL, new.Childrens, old.ID,
)
if err != nil {
return err
}
@@ -284,6 +240,10 @@ func (m *MySQL) UpdateBook(ctx context.Context, old, new *media.Book) error {
return nil
}
func (m *MySQL) DeleteBook(_ context.Context, b *media.Book) error {
return nil
}
func parseMigrationFileName(filename string) (int, string, error) {
sp := strings.SplitN(filename, "-", 2)
i, err := strconv.Atoi(sp[0])
+2
View File
@@ -100,6 +100,7 @@ func (c *DiscogsCache) loadRecordsFromFS(ctx context.Context) ([]media.Record, t
if err != nil {
return nil, time.Time{}, fmt.Errorf("error opening cache file %s: %w", c.persistFile, err)
}
defer f.Close()
err = gob.NewDecoder(f).Decode(p)
if err != nil {
return nil, time.Time{}, fmt.Errorf("error readhing from cache file %s: %w", c.persistFile, err)
@@ -120,6 +121,7 @@ func (c *DiscogsCache) saveRecordsToCache(ctx context.Context, records []media.R
if err != nil {
return fmt.Errorf("error opening cache file %s: %w", c.persistFile, err)
}
defer f.Close()
err = gob.NewEncoder(f).Encode(p)
if err != nil {
return fmt.Errorf("error writing to cache file %s: %w", c.persistFile, err)
+1 -1
View File
@@ -1,7 +1,7 @@
version: "3.8"
services:
mysql:
image: mysql:8.2
image: mysql:26.7
ports:
- 3306:3306
environment:
+163 -50
View File
@@ -3,61 +3,132 @@ var sortState = {
sortOrder: "asc",
};
function init() {
fetch("/api/books")
var admin = false;
var books;
function checkAdminMode() {
fetch("/api/mode")
.then((response) => response.json())
.then((books) => {
// prepare response
books.forEach(apiResponseParsing);
document.getElementById("search").addEventListener("input", (e) => {
renderTable(
search(
books,
e.target.value,
document.getElementById("childrens").checked
)
);
});
document.getElementById("childrens").addEventListener("change", (e) => {
renderTable(
search(
books,
document.getElementById("search").value,
e.target.checked
)
);
});
renderTable(
search(books, "", document.getElementById("childrens").checked)
);
.then((resp) => (admin = resp.Admin))
.then(() => {
if (admin) {
var element = document.getElementById("addBook");
element.addEventListener("click", (e) => {
e.preventDefault();
renderAddBookView();
});
element.classList.remove("hidden");
}
});
}
function renderTable(books, sortField) {
if (sortField) {
if (sortState.sortBy === sortField && sortState.sortOrder === "asc") {
sortState.sortOrder = "desc";
function loadBookList() {
fetch("/api/books")
.then((response) => response.json())
.then((list) => {
// prepare response
list.forEach(apiResponseParsing);
books = list;
document.getElementById("search").addEventListener("input", rerender);
document.getElementById("childrens").addEventListener("change", rerender);
rerender();
});
}
function rerender() {
var searchValue = document.getElementById("search").value;
var childrens = document.getElementById("childrens").checked;
renderTable(search(searchValue, childrens));
}
function init() {
checkAdminMode();
loadBookList();
}
function renderAddBookView() {
document.getElementById("current").innerHTML = AddBookTemplate();
document.getElementById("lookup").addEventListener("click", (e) => {
e.preventDefault();
if (document.getElementById("isbn-13").value.length === 13) {
getPossibleBooks(document.getElementById("isbn-13").value);
} else {
sortState.sortOrder = "asc";
console.log("no isbn");
}
});
document.getElementById("save").addEventListener("click", (e) => {
e.preventDefault();
saveBook({
title: document.getElementById("title").value,
authors: document.getElementById("authors").value.split(";"),
sortAuthor: document.getElementById("sortAuthor").value,
"isbn-10": document.getElementById("isbn-10").value,
"isbn-13": document.getElementById("isbn-13").value,
publisher: document.getElementById("publisher").value,
format: document.getElementById("format").value,
genre: document.getElementById("genre").value,
series: document.getElementById("series").value,
volume: document.getElementById("volume").value,
year: document.getElementById("year").value,
coverURL: document.getElementById("coverURL").value,
childrens: document.getElementById("childrens").checked,
signed: document.getElementById("signed").checked,
});
});
}
function getPossibleBooks(isbn) {
fetch("/api/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ "isbn-13": isbn }),
})
.then((response) => response.json())
.then((json) => {
Object.keys(json).forEach((key) => {
var elem = document.getElementById(key);
if (elem !== null) {
elem.value = json[key];
}
});
});
}
function saveBook(book) {
fetch("/api/books", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(book),
}).then(() => {
clearAddBookForm();
loadBookList();
});
}
function renderTable(bookList, sortField) {
if (sortField) {
sortState.sortOrder =
sortState.sortBy === sortField && sortState.sortOrder === "asc"
? "desc"
: "asc";
sortState.sortBy = sortField;
}
books.sort((one, two) =>
bookList.sort((one, two) =>
(one[sortState.sortBy] + one["sortTitle"]).localeCompare(
two[sortState.sortBy] + two["sortTitle"]
)
two[sortState.sortBy] + two["sortTitle"],
),
);
if (sortState.sortOrder === "desc") {
books.reverse();
bookList.reverse();
}
books.forEach((e, i) => (e.rowNumber = i)); // re-key
bookList.forEach((e, i) => (e.rowNumber = i)); // re-key
// rendering
var bookElement = document.getElementById("books");
bookElement.innerHTML = TableTemplate(books);
bookElement.innerHTML = TableTemplate(bookList);
var bookCount = document.getElementById("bookCount");
bookCount.innerHTML = `${books.length} books`;
document.getElementById("bookCount").innerHTML = `${bookList.length} books`;
// add listeners for selecting book to view
Array.from(bookElement.querySelectorAll("tbody tr"))
@@ -66,7 +137,7 @@ function renderTable(books, sortField) {
row.addEventListener("click", (e) => {
// add listener to swap current book
document.getElementById("current").innerHTML = BookTemplate(
books[e.currentTarget.id]
bookList[e.currentTarget.id],
);
});
});
@@ -74,9 +145,10 @@ function renderTable(books, sortField) {
Array.from(bookElement.querySelectorAll("tbody tr th[data-sort-by]")).forEach(
(row) => {
row.addEventListener("click", function (e) {
renderTable(books, e.target.dataset.sortBy); // only add callback when there's a sortBy attribute
// only add callback when there's a sortBy attribute
renderTable(bookList, e.target.dataset.sortBy);
});
}
},
);
// mark currently active column
bookElement
@@ -98,9 +170,9 @@ function apiResponseParsing(book) {
return book;
}
function search(books, searchBy, includeChildrensBooks) {
function search(searchBy, includeChildrensBooks) {
searchBy = searchCleaner(searchBy);
books = books.filter(
return books.filter(
({ title, authors, genre, publisher, series, year, childrens }) => {
var inSearch = true;
if (searchBy !== "") {
@@ -117,9 +189,8 @@ function search(books, searchBy, includeChildrensBooks) {
return inSearch && !childrens;
}
return inSearch;
}
},
);
return books;
}
function titleCleaner(title) {
@@ -145,14 +216,20 @@ function ISBNfromEAN(EAN) {
return ISBN + (checkdigit === 10 ? "X" : checkdigit);
}
function clearAddBookForm() {
document
.getElementById("newBookForm")
.childNodes.forEach((node) =>
node.nodeName === "LABEL" ? (node.lastChild.value = "") : null,
);
}
function BookTemplate({
"isbn-13": isbn13,
"isbn-10": isbn10,
authors,
coverURL,
description,
format,
notes,
publisher,
series,
signed,
@@ -173,6 +250,7 @@ function BookTemplate({
}
${signed ? "<span>Signed by the author ✒</span><br/>" : ""}
<span>${format}</span>
${admin ? `<a href="#">Edit Book</a>` : ""}
</div>`;
}
@@ -189,8 +267,10 @@ function TableRowTemplate({
return `<tr class="tRow" id="${rowNumber}">
<td class="title">
${title} ${
signed ? '<span class="signed" title="Signed by the author" >✒</span>' : ""
}
signed
? '<span class="signed" title="Signed by the author" >✒</span>'
: ""
}
</td>
<td class="author">${authors}</td>
<td class="publisher">${publisher}</td>
@@ -211,3 +291,36 @@ function TableTemplate(books) {
return acc.concat(TableRowTemplate(book));
}, "")} </table>`;
}
function AddBookTemplate() {
return `<div class="addBookView">
<div id="newBookForm">
${[
{ name: "Title", id: "title", type: "text" },
{ name: "Authors", id: "authors", type: "text" },
{ name: "SortAuthor", id: "sortAuthor", type: "text" },
{ name: "ISBN10", id: "isbn-10", type: "text" },
{ name: "ISBN13", id: "isbn-13", type: "text" },
{ name: "Publisher", id: "publisher", type: "text" },
{ name: "Format", id: "format", type: "text" },
{ name: "Genre", id: "genre", type: "text" },
{ name: "Series", id: "series", type: "text" },
{ name: "Volume", id: "volume", type: "text" },
{ name: "Year", id: "year", type: "text" },
{ name: "CoverURL", id: "coverURL", type: "text" },
{ name: "Signed", id: "signed", type: "checkbox" },
{ name: "Childrens", id: "childrens", type: "checkbox" },
].reduce((acc, field) => {
return acc.concat(
`<label>${field.name} <input
type="${field.type}"
name="${field.name.toLowerCase()}"
id="${field.id}"
/></label><br/>`,
);
}, "")}
<input id="lookup" type="submit" value="look up">
<input id="save" type="submit" value="save">
</div>
</div>`;
}
+1
View File
@@ -31,6 +31,7 @@
href="https://git.yetaga.in/alazyreader/library"
>git</a
>
<a href="#" id="addBook" class="hidden">add book</a>
<div id="searchBox">
<label for="childrens" class="bookCount"
>Include Childrens Books?</label
+2 -2
View File
@@ -185,7 +185,7 @@ body {
padding-left: 20px;
padding-right: 20px;
display: flex;
justify-content: space-evenly;
justify-content: space-evenly;
align-items: flex-start;
flex-wrap: wrap;
overflow: auto;
@@ -235,4 +235,4 @@ footer {
width: calc(100% - 40px);
color: #a29c77;
border-top: 2px solid #d8d0a0;
}
}
+4
View File
@@ -133,6 +133,10 @@ body {
overflow: hidden;
}
.hidden {
display: none;
}
#header {
height: 30px;
width: calc(100vw - 20px);
+51 -3
View File
@@ -1,10 +1,58 @@
module git.yetaga.in/alazyreader/library
go 1.16
go 1.27.0
require (
github.com/gdamore/tcell/v2 v2.6.0
github.com/go-sql-driver/mysql v1.7.1
git.yetaga.in/alazyreader/go-openlibrary v0.0.1
github.com/go-sql-driver/mysql v1.10.1
github.com/irlndts/go-discogs v0.3.6
github.com/kelseyhightower/envconfig v1.4.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20260921070245-7a4a4d6beae2
golang.org/x/sync v0.23.0
tailscale.com v1.102.4
)
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/creachadair/msync v0.8.1 // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gaissmai/bart v0.26.1 // indirect
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/jsimonetti/rtnetlink v1.4.1 // indirect
github.com/klauspost/compress v1.19.1 // indirect
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd // indirect
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect
)
+250 -47
View File
@@ -1,51 +1,254 @@
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q=
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
git.yetaga.in/alazyreader/go-openlibrary v0.0.1 h1:5juCi8d7YyNxXFvHytQNBww5E6GmPetM7nz3kVUqNQY=
git.yetaga.in/alazyreader/go-openlibrary v0.0.1/go.mod h1:o6zBFJTovdFcpA+As1bRFvk5PDhAs2Lf8iVzUt7dKw8=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek=
github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM=
github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU=
github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU=
github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30/go.mod h1:WueJeNDZvK1fMYEWJIkcivBfEzUkTpBhzlrUKKY8EuA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 h1:jn46zC9LdsVR/ZpMIJqMqb8hHv31BlLx3ulVqNspUOk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30/go.mod h1:1hTMsAgbdS/AtUi4bw8+gUuh1pceo+eXRLfpSuSQj3M=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 h1:mbRIur/BiHK6SKPjoBIXSE/hJ6g6JGRLuxQy1jGjlN4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13/go.mod h1:ITg9em2KbJx1s0y4aqRX5OYWG6HBZ5TVR//OdpEZ2CQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 h1:/Z5jmNrKsSD7EmDjzAPsm/3L9IuOkzaynklJZ1qX7S4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30/go.mod h1:lEzEZnOosE7zi8Z6royW1cFJTD9fpab4Ul1SBrllewk=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI=
github.com/aws/aws-sdk-go-v2/service/ssm v1.45.0 h1:IOdss+igJDFdic9w3WKwxGCmHqUxydvIhJOm9LJ32Dk=
github.com/aws/aws-sdk-go-v2/service/ssm v1.45.0/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio=
github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY=
github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ=
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creachadair/mds v0.25.13 h1:PsSUHV6zsfPd29k4kvm1rMoee1YFia7JyNGeMPmDcPM=
github.com/creachadair/mds v0.25.13/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs=
github.com/creachadair/msync v0.8.1 h1:QRd8si3qZ2Q4TaDL7tS/MG/lFE3YND7U7J9fy42eAFM=
github.com/creachadair/msync v0.8.1/go.mod h1:dt0bscS09J8Ie3AdccK9JpCb7LfStaDGlAmDLukOlY4=
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8=
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo=
github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME=
github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689 h1:0psnKZ+N2IP43/SZC8SKx6OpFJwLmQb9m9QyV9BC2f8=
github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689/go.mod h1:OGmRfY/9QEK2P5zCRtmqfbCF283xPkU2dvVA4MvbvpI=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I=
github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk=
github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U=
github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914 h1:kD8PseueGeYiid/Mmcv17Q0Qqicc4F46jcX22L/e/Hs=
github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/irlndts/go-discogs v0.3.6 h1:3oIJEkLGQ1ffJcoo6wvtawPI4/SyHoRpnu25Y51U4wg=
github.com/irlndts/go-discogs v0.3.6/go.mod h1:UVQ05FdCzH4P/usnSxQDh77QYE37HvmPnSCgogioljo=
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jsimonetti/rtnetlink v1.4.1 h1:JfD4jthWBqZMEffc5RjgmlzpYttAVw1sdnmiNaPO3hE=
github.com/jsimonetti/rtnetlink v1.4.1/go.mod h1:xJjT7t59UIZ62GLZbv6PLLo8VFrostJMPBAheR6OM8w=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.69.0 h1:OA85nJQS/T/MaYh/Q2CcgDKSGWqNIgrBDvDH85CuiNk=
github.com/prometheus/common v0.69.0/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/studio-b12/gowebdav v0.13.0 h1:OcwSg6IQHOFNdYHn3bPOHwSE8looG8N56Y5xTT1asqQ=
github.com/studio-b12/gowebdav v0.13.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d h1:JcGKBZAL7ePLwOhUdN8qGQZlP5GueEiIZwY7R62pejE=
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
github.com/tailscale/gliderssh v0.3.4-0.20260716005906-1a0f895faf28 h1:Azz5ILxxVsHN/KjIu3wkJPAmmtiijucZw4Ax5Ye8n+s=
github.com/tailscale/gliderssh v0.3.4-0.20260716005906-1a0f895faf28/go.mod h1:wn16Km1EZOX4UEAyaZa3dBwfFGOJ7neck40NcwosJUw=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
github.com/tailscale/golang-x-crypto v0.0.0-20260720153645-2ba0bf7866ed h1:uyvHhX1FQada0vVk8CSHa4tJT96EEAkTypaYz8Tq5Nc=
github.com/tailscale/golang-x-crypto v0.0.0-20260720153645-2ba0bf7866ed/go.mod h1:NC3xRCu4UR+m4n6ix8b6oLLbHa820Y0StbOQEdWTDo0=
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA=
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0 h1:CnIEL2n7Xql6Ux1k+Vu5S5ubDHCT/kxFgkKCY8FjefU=
github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg=
github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 h1:EBHQuS9qI8xJ96+YRgVV2ahFLUYbWpt1rf3wPfXN2wQ=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260902180247-86efde54dc70 h1:VwViOGcd7C8/Gs18efVlMxvUCS0un6KRKBPFEBvpUXg=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260902180247-86efde54dc70/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260921070245-7a4a4d6beae2 h1:eZbx26MSkMP0nFHSVwZplOsFCdjlfBz822hYXljWbUo=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260921070245-7a4a4d6beae2/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446 h1:cqHQ3AycTHvM2R7ikgyX57D+XvtcSnGylsLkOVhta/w=
golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA=
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.102.3 h1:M1czCAtMuIcg+2Z+FBPbJyAk3ZEQGEFKnvHthtE1c6M=
tailscale.com v1.102.3/go.mod h1:47bv91Xbg4K1p5wti7F1dmKvUVWV5BXF78d9EWJ+d6c=
tailscale.com v1.102.4 h1:FcAkb7MgfFFUIUASg18Mv5cAiraxb+eMgOQaOKqKyPo=
tailscale.com v1.102.4/go.mod h1:47bv91Xbg4K1p5wti7F1dmKvUVWV5BXF78d9EWJ+d6c=
+13
View File
@@ -0,0 +1,13 @@
package query
import (
"fmt"
"git.yetaga.in/alazyreader/library/media"
)
type Amazon struct{}
func (o *Amazon) GetByISBN(isbn string) (*media.Book, error) {
return nil, fmt.Errorf("unimplemented")
}
+28
View File
@@ -0,0 +1,28 @@
package query
import (
"fmt"
"strings"
)
func tryGetFirst(s []string) string {
if len(s) == 0 {
return ""
}
return s[0]
}
func buildTitle(title, subtitle string) string {
if subtitle != "" {
return fmt.Sprintf("%s: %s", title, subtitle)
}
return title
}
func getLastName(author string) string {
names := strings.Split(author, " ")
if len(names) < 2 {
return author
}
return names[len(names)-1]
}
+158
View File
@@ -0,0 +1,158 @@
package query
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"git.yetaga.in/alazyreader/library/media"
)
type GoogleBooks struct{}
type googleBookResult struct {
Kind string `json:"kind"`
TotalItems int `json:"totalItems"`
Items []item `json:"items"`
}
type industryIdentifier struct {
Type string `json:"type"`
Identifier string `json:"identifier"`
}
type readingMode struct {
Text bool `json:"text"`
Image bool `json:"image"`
}
type panelizationSummary struct {
ContainsEpubBubbles bool `json:"containsEpubBubbles"`
ContainsImageBubbles bool `json:"containsImageBubbles"`
}
type imageLink struct {
SmallThumbnail string `json:"smallThumbnail"`
Thumbnail string `json:"thumbnail"`
}
type volumeInfo struct {
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Authors []string `json:"authors"`
Publisher string `json:"publisher"`
PublishedDate string `json:"publishedDate"`
Description string `json:"description"`
IndustryIdentifiers []industryIdentifier `json:"industryIdentifiers"`
ReadingModes readingMode `json:"readingModes"`
PageCount int `json:"pageCount"`
PrintType string `json:"printType"`
Categories []string `json:"categories"`
AverageRating float64 `json:"averageRating"`
RatingsCount int `json:"ratingsCount"`
MaturityRating string `json:"maturityRating"`
AllowAnonLogging bool `json:"allowAnonLogging"`
ContentVersion string `json:"contentVersion"`
PanelizationSummary panelizationSummary `json:"panelizationSummary"`
ImageLinks imageLink `json:"imageLinks"`
Language string `json:"language"`
PreviewLink string `json:"previewLink"`
InfoLink string `json:"infoLink"`
CanonicalVolumeLink string `json:"canonicalVolumeLink"`
}
type saleInfo struct {
Country string `json:"country"`
Saleability string `json:"saleability"`
IsEbook bool `json:"isEbook"`
}
type epub struct {
IsAvailable bool `json:"isAvailable"`
}
type pdf struct {
IsAvailable bool `json:"isAvailable"`
}
type accessInfo struct {
Country string `json:"country"`
Viewability string `json:"viewability"`
Embeddable bool `json:"embeddable"`
PublicDomain bool `json:"publicDomain"`
TextToSpeechPermission string `json:"textToSpeechPermission"`
Epub epub `json:"epub"`
Pdf pdf `json:"pdf"`
WebReaderLink string `json:"webReaderLink"`
AccessViewStatus string `json:"accessViewStatus"`
QuoteSharingAllowed bool `json:"quoteSharingAllowed"`
}
type searchInfo struct {
TextSnippet string `json:"textSnippet"`
}
type item struct {
Kind string `json:"kind"`
ID string `json:"id"`
Etag string `json:"etag"`
SelfLink string `json:"selfLink"`
VolumeInfo volumeInfo `json:"volumeInfo"`
SaleInfo saleInfo `json:"saleInfo"`
AccessInfo accessInfo `json:"accessInfo"`
SearchInfo searchInfo `json:"searchInfo"`
}
func (g *GoogleBooks) GetByISBN(isbn string) (*media.Book, error) {
client := &http.Client{}
resp, err := client.Get(fmt.Sprintf("https://www.googleapis.com/books/v1/volumes?q=isbn:%s", isbn))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("received non-200 status code: %d", resp.StatusCode)
}
var result googleBookResult
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if err = json.Unmarshal(b, &result); err != nil {
return nil, err
}
if len(result.Items) == 0 {
return nil, fmt.Errorf("no book found")
}
return googleToBook(result.Items[0]), nil
}
func googleToBook(i item) *media.Book {
return &media.Book{
Title: buildTitle(i.VolumeInfo.Title, i.VolumeInfo.Subtitle),
Authors: i.VolumeInfo.Authors,
SortAuthor: strings.ToLower(getLastName(tryGetFirst(i.VolumeInfo.Authors))),
ISBN10: getIdentifierType(i.VolumeInfo.IndustryIdentifiers, "ISBN_10"),
ISBN13: getIdentifierType(i.VolumeInfo.IndustryIdentifiers, "ISBN_13"),
Publisher: i.VolumeInfo.Publisher,
Year: strings.Split(i.VolumeInfo.PublishedDate, "-")[0],
Description: i.VolumeInfo.Description,
Genre: tryGetFirst(i.VolumeInfo.Categories),
}
}
func getIdentifierType(iis []industryIdentifier, typename string) string {
for _, ident := range iis {
if ident.Type == typename {
return ident.Identifier
}
}
return ""
}
+11
View File
@@ -0,0 +1,11 @@
package query
import (
"git.yetaga.in/alazyreader/library/media"
)
type Null struct{}
func (o *Null) GetByISBN(isbn string) (*media.Book, error) {
return nil, nil
}
+46
View File
@@ -0,0 +1,46 @@
package query
import (
"strings"
"git.yetaga.in/alazyreader/go-openlibrary/client"
"git.yetaga.in/alazyreader/library/media"
)
type OpenLibrary struct {
client client.Client
}
func (o *OpenLibrary) GetByISBN(isbn string) (*media.Book, error) {
details, err := o.client.GetByISBN(isbn)
if err != nil {
return nil, err
}
return openLibraryToBook(details), nil
}
func openLibraryToBook(details *client.BookDetails) *media.Book {
return &media.Book{
Title: details.Title,
Authors: getAuthors(details.Authors),
SortAuthor: strings.ToLower(getLastName(tryGetFirst(getAuthors(details.Authors)))),
Publisher: getPublisher(details.Publishers),
ISBN10: tryGetFirst(details.Identifiers.ISBN10),
ISBN13: tryGetFirst(details.Identifiers.ISBN13),
}
}
func getPublisher(publishers []client.Publishers) string {
if len(publishers) == 0 {
return ""
}
return publishers[0].Name
}
func getAuthors(authors []client.Authors) []string {
ret := make([]string, len(authors))
for i, author := range authors {
ret[i] = author.Name
}
return ret
}
-140
View File
@@ -1,140 +0,0 @@
package ui
import (
"fmt"
"github.com/gdamore/tcell/v2"
)
type coord struct {
x, y int
}
type MockScreen struct {
x, y, h, w int
content map[coord]rune
}
func (m *MockScreen) Init() error {
m.content = map[coord]rune{}
return nil
}
func (m *MockScreen) Fini() {}
func (m *MockScreen) Clear() {
m.content = map[coord]rune{}
}
func (m *MockScreen) Fill(rune, tcell.Style) {}
func (m *MockScreen) SetCell(x int, y int, style tcell.Style, ch ...rune) {}
func (m *MockScreen) GetContent(x, y int) (mainc rune, combc []rune, style tcell.Style, width int) {
return m.content[coord{x, y}], nil, tcell.StyleDefault, 1
}
func (m *MockScreen) SetContent(x int, y int, mainc rune, combc []rune, style tcell.Style) {
m.content[coord{x, y}] = mainc
}
func (m *MockScreen) Suspend() error {
return nil
}
func (m *MockScreen) Resume() error {
return nil
}
func (m *MockScreen) SetStyle(style tcell.Style) {}
func (m *MockScreen) SetCursorStyle(style tcell.CursorStyle) {}
func (m *MockScreen) ShowCursor(x int, y int) {}
func (m *MockScreen) HideCursor() {}
func (m *MockScreen) Size() (int, int) {
return m.h, m.w
}
func (m *MockScreen) PollEvent() tcell.Event {
return tcell.NewEventError(fmt.Errorf("mock error"))
}
func (m *MockScreen) ChannelEvents(ch chan<- tcell.Event, quit <-chan struct{}) {}
func (m *MockScreen) HasPendingEvent() bool {
return false
}
func (m *MockScreen) PostEvent(ev tcell.Event) error {
return nil
}
func (m *MockScreen) PostEventWait(ev tcell.Event) {}
func (m *MockScreen) EnableMouse(...tcell.MouseFlags) {}
func (m *MockScreen) DisableMouse() {}
func (m *MockScreen) EnablePaste() {}
func (m *MockScreen) DisablePaste() {}
func (m *MockScreen) HasMouse() bool {
return false
}
func (m *MockScreen) Colors() int {
return 0
}
func (m *MockScreen) Show() {}
func (m *MockScreen) Sync() {}
func (m *MockScreen) CharacterSet() string {
return "UTF-8"
}
func (m *MockScreen) RegisterRuneFallback(r rune, subst string) {}
func (m *MockScreen) UnregisterRuneFallback(r rune) {}
func (m *MockScreen) CanDisplay(r rune, checkFallbacks bool) bool {
return true
}
func (m *MockScreen) Resize(x, y, h, w int) {
m.x, m.y, m.h, m.w = x, y, h, w
}
func (m *MockScreen) SetSize(h, w int) {
m.h, m.w = h, w
}
func (m *MockScreen) HasKey(tcell.Key) bool {
return true
}
func (m *MockScreen) Beep() error {
return nil
}
func (m *MockScreen) DumpContents() string {
var res string
for i := m.y; i < m.h; i++ {
str := []rune{}
for j := m.x; j < m.w; j++ {
r, ok := m.content[coord{x: j, y: i}]
if ok {
str = append(str, r)
} else {
str = append(str, ' ')
}
}
res = res + string(str) + "\n"
}
return res
}
-616
View File
@@ -1,616 +0,0 @@
package ui
import (
"strconv"
"strings"
"git.yetaga.in/alazyreader/library/media"
"github.com/gdamore/tcell/v2"
)
type Drawable interface {
Draw(tcell.Screen)
SetSize(x, y, h, w int)
SetStyle(tcell.Style)
SetVisible(bool)
}
type Offsets struct {
Top int
Bottom int
Left int
Right int
Percent int
}
type Contents []struct {
Offsets Offsets
Container Drawable
}
const (
LayoutUnmanaged = iota
LayoutHorizontalEven
LayoutVerticalEven
LayoutHorizontalPercent
LayoutVerticalPercent
)
var (
StyleActive = tcell.Style{}.Foreground(tcell.ColorWhite).Background(tcell.ColorBlack)
StyleInactive = tcell.Style{}.Foreground(tcell.ColorGray).Background(tcell.ColorBlack)
)
// A Container has no visible UI of its own, but arranges sub-components on the screen.
// layoutMethod manages how subcomponents are organized. If `LayoutUnmanaged`, it just uses the offsets
// in contents to paint on the screen. Otherwise, `LayoutHorizontalEven` and `LayoutVerticalEven` will
// have it compute even distributions of space for all components either horizontally or vertically,
// filling the container.
type Container struct {
x, y int
h, w int
layoutMethod int
contents Contents
visible bool
}
func NewContainer(contents Contents, layoutMethod int) *Container {
return &Container{
layoutMethod: layoutMethod,
contents: contents,
visible: true,
}
}
func (c *Container) Draw(s tcell.Screen) {
if !c.visible {
return
}
for i := range c.contents {
c.contents[i].Container.Draw(s)
}
}
func (c *Container) SetSize(x, y, h, w int) {
c.x, c.y, c.h, c.w = x, y, h, w
carry := 0
if c.layoutMethod == LayoutVerticalEven {
num := len(c.contents)
extra := c.h % num
for r := range c.contents {
w := c.w
h := c.h / num
x := c.x
y := c.y + (h * r) + carry
if extra > 0 { // distribute "extra" space to containers as we have some left
h++
extra--
carry++
}
c.contents[r].Container.SetSize(x, y, h, w)
}
} else if c.layoutMethod == LayoutHorizontalEven {
num := len(c.contents)
extra := c.w % num
for r := range c.contents {
w := c.w / num
h := c.h
x := c.x + (w * r) + carry
y := c.y
if extra > 0 { // distribute "extra" space to containers as we have some left
w++
extra--
carry++
}
c.contents[r].Container.SetSize(x, y, h, w)
}
} else if c.layoutMethod == LayoutHorizontalPercent {
// first, work out overall distribution
total := 0
for r := range c.contents {
// `0` or negatives are set as minimum
if c.contents[r].Offsets.Percent < 1 {
total += 1
} else {
total += c.contents[r].Offsets.Percent
}
}
carry := 0
// push around containers
for r := range c.contents {
ratio := (float64(c.contents[r].Offsets.Percent) / float64(total))
w := int(float64(c.w) * ratio)
h := c.h
x := c.x + carry
y := c.y
carry += w
// and add any remaining space to the last container
if r == len(c.contents)-1 {
w += (c.w - carry)
}
c.contents[r].Container.SetSize(x, y, h, w)
}
} else if c.layoutMethod == LayoutVerticalPercent {
// first, work out overall distribution
total := 0
for r := range c.contents {
// `0` or negatives are set as minimum
if c.contents[r].Offsets.Percent < 1 {
total += 1
} else {
total += c.contents[r].Offsets.Percent
}
}
carry := 0
// push around containers
for r := range c.contents {
ratio := (float64(c.contents[r].Offsets.Percent) / float64(total))
w := c.w
h := int(float64(c.h) * ratio)
x := c.x
y := c.y + carry
carry += h
// and add any remaining space to the last container
if r == len(c.contents)-1 {
h += (c.h - carry)
}
c.contents[r].Container.SetSize(x, y, h, w)
}
} else {
for r := range c.contents {
x := c.x + c.contents[r].Offsets.Left
y := c.y + c.contents[r].Offsets.Top
h := c.h - c.contents[r].Offsets.Bottom
w := c.w - c.contents[r].Offsets.Right
c.contents[r].Container.SetSize(x, y, h, w)
}
}
}
func (c *Container) SetStyle(s tcell.Style) {
// containers have no visible elements to style
}
func (c *Container) SetVisible(b bool) {
c.visible = b
}
func (c *Container) Contents() Contents {
return c.contents
}
func (c *Container) SetContents(con Contents) {
c.contents = con
}
// A Box draws a ASCII box around its contents, with an optional title and footer.
type Box struct {
x, y int
h, w int
title Drawable
menuItems Drawable
contents Contents
style tcell.Style
cascade bool
visible bool
transparent bool
}
func NewBox(title string, menuItems []string, contents Contents, initialStyle tcell.Style, cascade bool) *Box {
return &Box{
title: NewPaddedText(title),
menuItems: NewPaddedText(strings.Join(menuItems, " ")),
contents: contents,
style: initialStyle,
cascade: cascade,
visible: true,
transparent: false,
}
}
func (b *Box) SetSize(x, y, h, w int) {
b.x, b.y, b.h, b.w = x, y, h, w
b.title.SetSize(b.x+2, b.y, 0, 0)
b.menuItems.SetSize(b.x+2, b.y+b.h-1, 0, 0)
for c := range b.contents {
x := b.x + b.contents[c].Offsets.Left
y := b.y + b.contents[c].Offsets.Top
h := b.h - b.contents[c].Offsets.Bottom
w := b.w - b.contents[c].Offsets.Right
b.contents[c].Container.SetSize(x, y, h, w)
}
}
func (b *Box) Draw(s tcell.Screen) {
if !b.visible {
return
}
// blank out inner area
if !b.transparent {
for m := b.x + 1; m < b.x+b.w-1; m++ {
for n := b.y + 1; n < b.y+b.h-1; n++ {
s.SetContent(m, n, ' ', nil, b.style)
}
}
}
// draw outside bars
for m := b.x + 1; m < b.x+b.w-1; m++ {
s.SetContent(m, b.y, tcell.RuneHLine, nil, b.style)
s.SetContent(m, b.y+b.h-1, tcell.RuneHLine, nil, b.style)
}
for m := b.y + 1; m < b.y+b.h-1; m++ {
s.SetContent(b.x, m, tcell.RuneVLine, nil, b.style)
s.SetContent(b.x+b.w-1, m, tcell.RuneVLine, nil, b.style)
}
s.SetContent(b.x, b.y, tcell.RuneULCorner, nil, b.style)
s.SetContent(b.x+b.w-1, b.y, tcell.RuneURCorner, nil, b.style)
s.SetContent(b.x, b.y+b.h-1, tcell.RuneLLCorner, nil, b.style)
s.SetContent(b.x+b.w-1, b.y+b.h-1, tcell.RuneLRCorner, nil, b.style)
if b.title != nil {
b.title.Draw(s)
}
if b.menuItems != nil {
b.menuItems.Draw(s)
}
for c := range b.contents {
b.contents[c].Container.Draw(s)
}
}
func (b *Box) SetStyle(s tcell.Style) {
b.style = s
b.title.SetStyle(s)
b.menuItems.SetStyle(s)
if b.cascade {
for c := range b.contents {
b.contents[c].Container.SetStyle(s)
}
}
}
func (b *Box) SetVisible(v bool) {
b.visible = v
}
func (b *Box) SetTransparent(v bool) {
b.transparent = v
}
func (b *Box) Contents() Contents {
return b.contents
}
func (b *Box) SetContents(c Contents) {
b.contents = c
}
// A List is a scrollable, pageable list with a selector token.
type List struct {
x, y int
h, w int
selected int
listItems []ListKeyValue
style tcell.Style
visible bool
}
type ListKeyValue struct {
Key int
Value string
}
func NewList(listItems []ListKeyValue, initialSelected int) *List {
return &List{
listItems: listItems,
selected: initialSelected,
visible: true,
}
}
func (l *List) SetSize(x, y, h, w int) {
l.x, l.y, l.h, l.w = x, y, h, w
}
func (l *List) Draw(s tcell.Screen) {
if !l.visible {
return
}
for i := range l.listItems {
for j, r := range l.listItems[i].Value {
s.SetContent(l.x+j, l.y+i, r, nil, l.style)
}
if i == l.selected {
s.SetContent(l.x+len(l.listItems[i].Value)+1, l.y+i, '<', nil, l.style)
}
}
}
func (l *List) SetVisible(b bool) {
l.visible = b
}
func (l *List) SetStyle(s tcell.Style) {
l.style = s
}
func (l *List) Selected() int {
return l.selected
}
func (l *List) SelectedID() int {
if l.listItems == nil || len(l.listItems) == 0 {
return 0
}
return l.listItems[l.selected].Key
}
func (l *List) SetSelected(i int) {
l.selected = i
}
func (l *List) ListMembers() []ListKeyValue {
return l.listItems
}
func (l *List) SetMembers(lkv []ListKeyValue) {
l.listItems = lkv
}
// BookDetails displays an editable list of book details
type BookDetails struct {
x, y int
h, w int
book *media.Book
style tcell.Style
visible bool
}
func NewBookDetails(b *media.Book) *BookDetails {
return &BookDetails{
book: b,
visible: true,
}
}
func (l *BookDetails) SetBook(b *media.Book) {
l.book = b
}
func (l *BookDetails) SetSize(x, y, h, w int) {
l.x, l.y, l.h, l.w = x, y, h, w
}
func (l *BookDetails) Draw(s tcell.Screen) {
if l.book == nil {
return
}
if !l.visible {
return
}
items := []struct {
label string
value string
}{
{"Title", l.book.Title},
{"Authors", strings.Join(l.book.Authors, ", ")},
{"Sort Author", l.book.SortAuthor},
{"ISBN-10", l.book.ISBN10},
{"ISBN-13", l.book.ISBN13},
{"Format", l.book.Format},
{"Genre", l.book.Genre},
{"Publisher", l.book.Publisher},
{"Series", l.book.Series},
{"Volume", l.book.Volume},
{"Year", l.book.Year},
{"Signed", strconv.FormatBool(l.book.Signed)},
{"Cover URL", l.book.CoverURL},
{"Notes", l.book.Notes},
{"Description", l.book.Description},
}
for i := range items {
if i < l.h-2 {
kv := NewKeyValue(items[i].label, ": ", items[i].value)
kv.SetSize(l.x, l.y+i, 0, 0)
kv.SetStyle(l.style)
kv.Draw(s)
}
}
}
func (l *BookDetails) SetVisible(b bool) {
l.visible = b
}
func (l *BookDetails) SetStyle(s tcell.Style) {
l.style = s
}
// PaddedText outputs strings with a space on both sides.
// Useful for generating headings, footers, etc. Used by Box.
type PaddedText struct {
x, y int
h, w int
text string
style tcell.Style
visible bool
}
func NewPaddedText(text string) *PaddedText {
return &PaddedText{text: text, visible: true}
}
func (p *PaddedText) SetSize(x, y, _, _ int) {
p.x, p.y, p.h, p.w = x, y, 1, len(p.text)+2
}
func (p *PaddedText) SetStyle(s tcell.Style) {
p.style = s
}
func (p *PaddedText) Draw(s tcell.Screen) {
if p.text == "" {
return
}
if !p.visible {
return
}
t := p.x
s.SetContent(t, p.y, ' ', nil, p.style)
t++
for _, r := range p.text {
s.SetContent(t, p.y, r, nil, p.style)
t++
}
s.SetContent(t, p.y, ' ', nil, p.style)
}
func (p *PaddedText) SetVisible(b bool) {
p.visible = b
}
type KeyValue struct {
x, y int
h, w int
key string
value string
separator string
style tcell.Style
visible bool
}
func NewKeyValue(key, separator, value string) *KeyValue {
return &KeyValue{
key: key,
separator: separator,
value: value,
visible: true,
}
}
func (p *KeyValue) SetSize(x, y, _, _ int) {
p.x, p.y, p.h, p.w = x, y, 1, len(p.key)+len(p.separator)+len(p.value)
}
func (p *KeyValue) SetStyle(s tcell.Style) {
p.style = s
}
func (p *KeyValue) Draw(s tcell.Screen) {
if !p.visible {
return
}
for j, r := range p.key {
s.SetContent(p.x+j, p.y, r, nil, p.style)
}
for j, r := range p.separator {
s.SetContent(p.x+len(p.key)+j, p.y, r, nil, p.style)
}
for j, r := range p.value {
s.SetContent(p.x+len(p.key)+len(p.separator)+j, p.y, r, nil, p.style)
}
}
func (p *KeyValue) SetVisible(b bool) {
p.visible = b
}
func (p *KeyValue) GetValue() string {
return p.value
}
type EditableTextLine struct {
x, y int
h, w int
text string
style tcell.Style
visible bool
cursorPos int
showCursor bool
}
func NewEditableTextLine(initialText string) *EditableTextLine {
return &EditableTextLine{
text: initialText,
visible: true,
showCursor: true,
}
}
func (p *EditableTextLine) SetSize(x, y, _, _ int) {
p.x, p.y, p.h, p.w = x, y, 1, len(p.text)
}
func (p *EditableTextLine) SetStyle(s tcell.Style) {
p.style = s
}
func (p *EditableTextLine) Draw(s tcell.Screen) {
if !p.visible {
return
}
for j, r := range p.text {
s.SetContent(p.x+j, p.y, r, nil, p.style)
}
s.ShowCursor(p.x+p.cursorPos, p.y)
}
func (p *EditableTextLine) SetVisible(b bool) {
p.visible = b
}
func (p *EditableTextLine) SetCursorVisible(b bool) {
p.showCursor = b
}
func (p *EditableTextLine) SetText(t string) {
p.text = t
if len(p.text) == 0 {
p.ResetCursor(true)
return
}
p.ResetCursor(false)
}
func (p *EditableTextLine) Text() string {
return p.text
}
func (p *EditableTextLine) ResetCursor(beginning bool) {
if beginning {
p.cursorPos = 0
} else {
p.cursorPos = len(p.text)
}
}
func (p *EditableTextLine) InsertAtCursor(r rune) {
if len(p.text) == 0 {
p.text = string(r)
p.cursorPos = 1
return
}
p.text = p.text[0:p.cursorPos] + string(r) + p.text[p.cursorPos:len(p.text)]
p.cursorPos = p.cursorPos + 1
}
func (p *EditableTextLine) MoveCursor(i int) {
if p.cursorPos+i < 0 {
p.cursorPos = 0
return
}
if p.cursorPos+i > len(p.text) {
p.cursorPos = len(p.text)
return
}
p.cursorPos = p.cursorPos + i
}
func (p *EditableTextLine) DeleteAtCursor() {
if len(p.text) == 0 {
p.cursorPos = 0
return
}
p.text = p.text[0:p.cursorPos-1] + p.text[p.cursorPos:len(p.text)]
p.cursorPos = p.cursorPos - 1
}
-241
View File
@@ -1,241 +0,0 @@
package ui
import (
"fmt"
"testing"
"github.com/gdamore/tcell/v2"
)
func TestContainerOneBox(t *testing.T) {
expect := ` box one
`
m := &MockScreen{}
one := NewBox("box one", nil, Contents{}, tcell.Style{}, false)
container := NewContainer(
Contents{{Container: one}},
LayoutHorizontalEven,
)
m.Init()
m.Resize(0, 0, 5, 20)
container.SetSize(0, 0, 5, 20)
container.Draw(m)
result := m.DumpContents()
if result != expect {
fmt.Printf("expected:\n%+v", expect)
fmt.Printf("actual:\n%+v", result)
t.Fail()
}
}
func TestContainerTwoBoxesHStack(t *testing.T) {
expect := ` one two
`
m := &MockScreen{}
one := NewBox("one", nil, Contents{}, tcell.Style{}, false)
two := NewBox("two", nil, Contents{}, tcell.Style{}, false)
container := NewContainer(
Contents{{Container: one}, {Container: two}},
LayoutHorizontalEven,
)
m.Init()
m.Resize(0, 0, 5, 20)
container.SetSize(0, 0, 5, 20)
container.Draw(m)
result := m.DumpContents()
if result != expect {
fmt.Printf("expected:\n%+v", expect)
fmt.Printf("actual:\n%+v", result)
t.Fail()
}
}
func TestContainerThreeBoxesUnevenHStack(t *testing.T) {
expect := ` one two three
`
m := &MockScreen{}
one := NewBox("one", nil, Contents{}, tcell.Style{}, false)
two := NewBox("two", nil, Contents{}, tcell.Style{}, false)
three := NewBox("three", nil, Contents{}, tcell.Style{}, false)
container := NewContainer(
Contents{{Container: one}, {Container: two}, {Container: three}},
LayoutHorizontalEven,
)
m.Init()
m.Resize(0, 0, 5, 29)
container.SetSize(0, 0, 5, 29)
container.Draw(m)
result := m.DumpContents()
if result != expect {
fmt.Printf("expected:\n%+v", expect)
fmt.Printf("actual:\n%+v", result)
t.Fail()
}
}
func TestContainerTwoBoxesHPercentStack(t *testing.T) {
expect := ` one two
`
m := &MockScreen{}
one := NewBox("one", nil, Contents{}, tcell.Style{}, false)
two := NewBox("two", nil, Contents{}, tcell.Style{}, false)
container := NewContainer(
Contents{
{Container: one, Offsets: Offsets{Percent: 2}},
{Container: two, Offsets: Offsets{Percent: 1}}},
LayoutHorizontalPercent,
)
m.Init()
m.Resize(0, 0, 5, 22)
container.SetSize(0, 0, 5, 22)
container.Draw(m)
result := m.DumpContents()
if result != expect {
fmt.Printf("expected:\n%+v", expect)
fmt.Printf("actual:\n%+v", result)
t.Fail()
}
}
func TestContainerTwoBoxesVStack(t *testing.T) {
expect := ` one
two
`
m := &MockScreen{}
one := NewBox("one", nil, Contents{}, tcell.Style{}, false)
two := NewBox("two", nil, Contents{}, tcell.Style{}, false)
container := NewContainer(
Contents{{Container: one}, {Container: two}},
LayoutVerticalEven,
)
m.Init()
m.Resize(0, 0, 10, 10)
container.SetSize(0, 0, 10, 10)
container.Draw(m)
result := m.DumpContents()
if result != expect {
fmt.Printf("expected:\n%+v", expect)
fmt.Printf("actual:\n%+v", result)
t.Fail()
}
}
func TestContainerTwoBoxesPercentageVStack(t *testing.T) {
expect := ` one
two
`
m := &MockScreen{}
one := NewBox("one", nil, Contents{}, tcell.Style{}, false)
two := NewBox("two", nil, Contents{}, tcell.Style{}, false)
container := NewContainer(
Contents{
{Container: one, Offsets: Offsets{Percent: 2}},
{Container: two, Offsets: Offsets{Percent: 1}}},
LayoutVerticalPercent,
)
m.Init()
m.Resize(0, 0, 10, 10)
container.SetSize(0, 0, 10, 10)
container.Draw(m)
result := m.DumpContents()
if result != expect {
fmt.Printf("expected:\n%+v", expect)
fmt.Printf("actual:\n%+v", result)
t.Fail()
}
}
func TestNewEditableTextLine(t *testing.T) {
e := NewEditableTextLine("")
e.InsertAtCursor('a')
e.InsertAtCursor('b')
e.InsertAtCursor('c')
if e.text != "abc" {
fmt.Printf("expected: 'abc', actual: '%+v'", e.text)
t.Fail()
}
e.MoveCursor(-1)
e.InsertAtCursor('d')
if e.text != "abdc" {
fmt.Printf("expected: 'abdc', actual: '%+v'", e.text)
t.Fail()
}
e.MoveCursor(-20)
e.InsertAtCursor('e')
if e.text != "eabdc" {
fmt.Printf("expected: 'eabdc', actual: '%+v'", e.text)
t.Fail()
}
e.MoveCursor(20)
e.InsertAtCursor('f')
if e.text != "eabdcf" {
fmt.Printf("expected: 'eabdcf', actual: '%+v'", e.text)
t.Fail()
}
e.MoveCursor(1)
e.InsertAtCursor('g')
if e.text != "eabdcfg" {
fmt.Printf("expected: 'eabdcfg', actual: '%+v'", e.text)
t.Fail()
}
e.DeleteAtCursor()
e.DeleteAtCursor()
e.MoveCursor(-1)
e.DeleteAtCursor()
if e.text != "eabc" {
fmt.Printf("expected: 'eabc', actual: '%+v'", e.text)
t.Fail()
}
e.ResetCursor(false)
e.InsertAtCursor('h')
e.ResetCursor(true)
e.InsertAtCursor('g')
if e.text != "geabch" {
fmt.Printf("expected: 'geabch', actual: '%+v'", e.text)
t.Fail()
}
e.SetText("the rain in spain")
e.InsertAtCursor('s')
if e.text != "the rain in spains" {
fmt.Printf("expected: 'the rain in spains', actual: '%+v'", e.text)
t.Fail()
}
e.SetText("")
e.InsertAtCursor('s')
if e.text != "s" {
fmt.Printf("expected: 's', actual: '%+v'", e.text)
t.Fail()
}
}