fujin-cli 0.22.0__tar.gz → 0.22.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/CHANGELOG.md +12 -0
  2. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/PKG-INFO +1 -1
  3. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-1password/pyproject.toml +2 -2
  4. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-bitwarden/pyproject.toml +2 -2
  5. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-doppler/pyproject.toml +2 -2
  6. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/pyproject.toml +2 -2
  7. fujin_cli-0.22.2/src/fujin/__init__.py +1 -0
  8. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/_installer.py +9 -3
  9. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/config.py +1 -0
  10. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/connection.py +27 -41
  11. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/uv.lock +5 -5
  12. fujin_cli-0.22.0/src/fujin/__init__.py +0 -1
  13. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/.github/FUNDING.yml +0 -0
  14. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/.github/workflows/publish.yml +0 -0
  15. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/.github/workflows/test.yml +0 -0
  16. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/.gitignore +0 -0
  17. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/.pre-commit-config.yaml +0 -0
  18. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/.readthedocs.yaml +0 -0
  19. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/CLAUDE.md +0 -0
  20. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/LICENSE.txt +0 -0
  21. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/README.md +0 -0
  22. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/Vagrantfile +0 -0
  23. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-cat-help.png +0 -0
  24. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-exec-help.png +0 -0
  25. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-help.png +0 -0
  26. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-logs-help.png +0 -0
  27. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-restart-help.png +0 -0
  28. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-scale-help.png +0 -0
  29. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-shell-help.png +0 -0
  30. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-start-help.png +0 -0
  31. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-status-help.png +0 -0
  32. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/app-stop-help.png +0 -0
  33. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/audit-help.png +0 -0
  34. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/deploy-help.png +0 -0
  35. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/down-help.png +0 -0
  36. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/fa-help.png +0 -0
  37. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/fujin-help.png +0 -0
  38. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/init-help.png +0 -0
  39. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/migrate-help.png +0 -0
  40. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/new-help.png +0 -0
  41. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/prune-help.png +0 -0
  42. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/rollback-help.png +0 -0
  43. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/server-bootstrap-help.png +0 -0
  44. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/server-create-user-help.png +0 -0
  45. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/server-exec-help.png +0 -0
  46. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/server-help.png +0 -0
  47. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/server-setup-ssh-help.png +0 -0
  48. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/server-status-help.png +0 -0
  49. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/_static/images/help/up-help.png +0 -0
  50. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/changelog.rst +0 -0
  51. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/app.rst +0 -0
  52. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/audit.rst +0 -0
  53. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/deploy.rst +0 -0
  54. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/down.rst +0 -0
  55. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/index.rst +0 -0
  56. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/init.rst +0 -0
  57. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/migrate.rst +0 -0
  58. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/new.rst +0 -0
  59. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/prune.rst +0 -0
  60. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/rollback.rst +0 -0
  61. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/server.rst +0 -0
  62. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/commands/up.rst +0 -0
  63. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/conf.py +0 -0
  64. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/configuration.rst +0 -0
  65. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/guides/django-complete.rst +0 -0
  66. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/guides/index.rst +0 -0
  67. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/guides/templates.rst +0 -0
  68. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/howtos/binary.rst +0 -0
  69. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/howtos/django.rst +0 -0
  70. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/howtos/index.rst +0 -0
  71. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/index.rst +0 -0
  72. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/installation.rst +0 -0
  73. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/integrations.rst +0 -0
  74. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/requirements.txt +0 -0
  75. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/secrets.rst +0 -0
  76. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/docs/troubleshooting.rst +0 -0
  77. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/.fujin/Caddyfile +0 -0
  78. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/.fujin/systemd/health.service +0 -0
  79. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/.fujin/systemd/health.timer +0 -0
  80. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/.fujin/systemd/web.service +0 -0
  81. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/.fujin/systemd/worker@.service +0 -0
  82. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/README.md +0 -0
  83. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/bookstore/__init__.py +0 -0
  84. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/bookstore/__main__.py +0 -0
  85. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/bookstore/asgi.py +0 -0
  86. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/bookstore/management/commands/health.py +0 -0
  87. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/bookstore/settings.py +0 -0
  88. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/bookstore/urls.py +0 -0
  89. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/bookstore/wsgi.py +0 -0
  90. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/fujin.toml +0 -0
  91. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/manage.py +0 -0
  92. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/pyproject.toml +0 -0
  93. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/examples/django/bookstore/requirements.txt +0 -0
  94. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/justfile +0 -0
  95. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-1password/README.md +0 -0
  96. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-1password/src/fujin_secrets_1password/__init__.py +0 -0
  97. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-1password/src/fujin_secrets_1password/py.typed +0 -0
  98. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-bitwarden/README.md +0 -0
  99. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-bitwarden/src/fujin_secrets_bitwarden/__init__.py +0 -0
  100. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-bitwarden/src/fujin_secrets_bitwarden/py.typed +0 -0
  101. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-doppler/README.md +0 -0
  102. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-doppler/src/fujin_secrets_doppler/__init__.py +0 -0
  103. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/plugins/fujin-secrets-doppler/src/fujin_secrets_doppler/py.typed +0 -0
  104. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/__main__.py +0 -0
  105. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/audit.py +0 -0
  106. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/caddy.py +0 -0
  107. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/__init__.py +0 -0
  108. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/_base.py +0 -0
  109. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/app.py +0 -0
  110. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/audit.py +0 -0
  111. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/deploy.py +0 -0
  112. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/down.py +0 -0
  113. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/init.py +0 -0
  114. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/migrate.py +0 -0
  115. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/new.py +0 -0
  116. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/prune.py +0 -0
  117. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/rollback.py +0 -0
  118. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/server.py +0 -0
  119. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/showenv.py +0 -0
  120. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/commands/up.py +0 -0
  121. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/discovery.py +0 -0
  122. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/errors.py +0 -0
  123. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/fa.py +0 -0
  124. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/formatting.py +0 -0
  125. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/secrets.py +0 -0
  126. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/src/fujin/templates.py +0 -0
  127. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/__init__.py +0 -0
  128. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/conftest.py +0 -0
  129. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/integration/Dockerfile +0 -0
  130. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/integration/__init__.py +0 -0
  131. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/integration/conftest.py +0 -0
  132. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/integration/helpers.py +0 -0
  133. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/integration/test_app_management.py +0 -0
  134. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/integration/test_full_deploy.py +0 -0
  135. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/integration/test_installation.py +0 -0
  136. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/integration/test_server_bootstrap.py +0 -0
  137. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_app.py +0 -0
  138. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_audit.py +0 -0
  139. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_caddy_domain.py +0 -0
  140. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_config.py +0 -0
  141. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_connection.py +0 -0
  142. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_deploy.py +0 -0
  143. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_discovery.py +0 -0
  144. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_down.py +0 -0
  145. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_init.py +0 -0
  146. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_new.py +0 -0
  147. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_prune.py +0 -0
  148. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_rollback.py +0 -0
  149. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_scale.py +0 -0
  150. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_secrets.py +0 -0
  151. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_server.py +0 -0
  152. {fujin_cli-0.22.0 → fujin_cli-0.22.2}/tests/test_up.py +0 -0
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.22.2] - 2026-03-21
8
+
9
+ ### 🐛 Bug Fixes
10
+
11
+ - Handle pre-existing group when creating app user
12
+
13
+ ## [0.22.1] - 2026-03-21
14
+
15
+ ### 🐛 Bug Fixes
16
+
17
+ - Use native ssh auth first when needed
18
+
7
19
  ## [0.22.0] - 2026-03-20
8
20
 
9
21
  ### 🚀 Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fujin-cli
3
- Version: 0.22.0
3
+ Version: 0.22.2
4
4
  Summary: Get your project up and running in a few minutes on your own vps.
5
5
  Project-URL: Documentation, https://github.com/Tobi-De/fujin#readme
6
6
  Project-URL: Issues, https://github.com/Tobi-De/fujin/issues
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.9.18,<0.10" ]
4
4
 
5
5
  [project]
6
6
  name = "fujin-secrets-1password"
7
- version = "0.22.0"
7
+ version = "0.22.2"
8
8
  description = "1Password secret adapter for Fujin"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.14",
21
21
  ]
22
22
  dependencies = [
23
- "fujin-cli>=0.22",
23
+ "fujin-cli>=0.22.2",
24
24
  "python-dotenv>=1.0.1",
25
25
  ]
26
26
 
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.9.18,<0.10" ]
4
4
 
5
5
  [project]
6
6
  name = "fujin-secrets-bitwarden"
7
- version = "0.22.0"
7
+ version = "0.22.2"
8
8
  description = "Bitwarden secret adapter for Fujin"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.14",
21
21
  ]
22
22
  dependencies = [
23
- "fujin-cli>=0.22",
23
+ "fujin-cli>=0.22.2",
24
24
  "python-dotenv>=1.0.1",
25
25
  ]
26
26
 
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.9.18,<0.10" ]
4
4
 
5
5
  [project]
6
6
  name = "fujin-secrets-doppler"
7
- version = "0.22.0"
7
+ version = "0.22.2"
8
8
  description = "Doppler secret adapter for Fujin"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.14",
21
21
  ]
22
22
  dependencies = [
23
- "fujin-cli>=0.22",
23
+ "fujin-cli>=0.22.2",
24
24
  "python-dotenv>=1.0.1",
25
25
  ]
26
26
 
@@ -5,7 +5,7 @@ requires = [ "hatchling" ]
5
5
 
6
6
  [project]
7
7
  name = "fujin-cli"
8
- version = "0.22.0"
8
+ version = "0.22.2"
9
9
  description = "Get your project up and running in a few minutes on your own vps."
10
10
  readme = "README.md"
11
11
  keywords = [
@@ -149,7 +149,7 @@ markers = [
149
149
  ]
150
150
 
151
151
  [tool.bumpversion]
152
- current_version = "0.22.0"
152
+ current_version = "0.22.2"
153
153
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
154
154
  serialize = [ "{major}.{minor}.{patch}" ]
155
155
  search = "{current_version}"
@@ -0,0 +1 @@
1
+ __version__ = "0.22.2"
@@ -118,9 +118,15 @@ def install(
118
118
  logger.debug("User %s already exists", config.app_user)
119
119
  except KeyError:
120
120
  logger.debug("Creating system user: %s", config.app_user)
121
- run(
122
- f"useradd --system --no-create-home --shell /usr/sbin/nologin {config.app_user}",
123
- )
121
+ # Check if group already exists (e.g., from a previous partial install)
122
+ try:
123
+ grp.getgrnam(config.app_user)
124
+ # Group exists, use it instead of creating a new one
125
+ useradd_cmd = f"useradd --system --no-create-home --shell /usr/sbin/nologin --no-user-group -g {config.app_user} {config.app_user}"
126
+ except KeyError:
127
+ # No existing group, let useradd create one
128
+ useradd_cmd = f"useradd --system --no-create-home --shell /usr/sbin/nologin {config.app_user}"
129
+ run(useradd_cmd, check=True)
124
130
 
125
131
  app_dir = Path(config.app_dir)
126
132
  app_dir.mkdir(parents=True, exist_ok=True)
@@ -212,6 +212,7 @@ class HostConfig(msgspec.Struct, kw_only=True):
212
212
  port: int = 22
213
213
  _key_filename: str | None = msgspec.field(name="key_filename", default=None)
214
214
  key_passphrase_env: str | None = None
215
+ native_ssh_auth: bool = False # Use native ssh for auth (for Tailscale, etc.)
215
216
 
216
217
  def __post_init__(self):
217
218
  if self._env_file and self.env_content:
@@ -344,8 +344,34 @@ def connection(host: HostConfig) -> Generator[SSH2Connection, None, None]:
344
344
  auth_methods_tried = []
345
345
  authenticated = False
346
346
 
347
+ # For hosts that require native SSH auth (Tailscale, etc.), authenticate via ssh first
348
+ if host.native_ssh_auth:
349
+ logger.info("Using native SSH for authentication...")
350
+ ssh_cmd = [
351
+ "ssh",
352
+ "-p",
353
+ str(host.port),
354
+ "-o",
355
+ "StrictHostKeyChecking=accept-new",
356
+ f"{host.user}@{host.address}",
357
+ "exit",
358
+ ]
359
+ result = subprocess.run(ssh_cmd)
360
+ if result.returncode == 0:
361
+ # Reconnect after successful auth - session is now authorized
362
+ sock.close()
363
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
364
+ sock.settimeout(30)
365
+ sock.connect((host.address, host.port))
366
+ sock.settimeout(None)
367
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
368
+ session = Session()
369
+ session.handshake(sock)
370
+ auth_methods_tried.append("native ssh")
371
+ # Continue to normal auth methods below (they should work now)
372
+
347
373
  # Method 1: Explicit key file (if specified)
348
- if host.key_filename:
374
+ if not authenticated and host.key_filename:
349
375
  try:
350
376
  key_path = Path(host.key_filename).expanduser()
351
377
  logger.debug(f"Trying explicit key: {key_path}")
@@ -416,46 +442,6 @@ def connection(host: HostConfig) -> Generator[SSH2Connection, None, None]:
416
442
  logger.debug(f"Password auth failed: {e}")
417
443
  auth_methods_tried.append("password (failed)")
418
444
 
419
- # Method 5: Keyboard-interactive via native SSH (for Tailscale, etc.)
420
- if not authenticated:
421
- try:
422
- available_methods = session.userauth_list(host.user)
423
- if available_methods and "keyboard-interactive" in available_methods:
424
- logger.info(
425
- "Trying keyboard-interactive auth via native SSH (Tailscale, etc.)..."
426
- )
427
- # Spawn native ssh to handle interactive auth (shows URL, waits for browser)
428
- ssh_cmd = [
429
- "ssh",
430
- "-p",
431
- str(host.port),
432
- "-o",
433
- "StrictHostKeyChecking=accept-new",
434
- f"{host.user}@{host.address}",
435
- "exit",
436
- ]
437
- result = subprocess.run(ssh_cmd)
438
- if result.returncode == 0:
439
- # Auth succeeded via native SSH, retry with ssh2-python
440
- # Need to create a new session since the old one may be in a bad state
441
- sock.close()
442
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
443
- sock.settimeout(30)
444
- sock.connect((host.address, host.port))
445
- sock.settimeout(None)
446
- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
447
- session = Session()
448
- session.handshake(sock)
449
- # Retry agent auth (Tailscale adds credentials to agent after browser auth)
450
- session.agent_auth(host.user)
451
- authenticated = session.userauth_authenticated()
452
- if authenticated:
453
- logger.info("✓ Authenticated after keyboard-interactive flow")
454
- auth_methods_tried.append("keyboard-interactive (native ssh)")
455
- except Exception as e:
456
- logger.debug(f"Keyboard-interactive auth failed: {e}")
457
- auth_methods_tried.append("keyboard-interactive (failed)")
458
-
459
445
  if not authenticated:
460
446
  sock.close()
461
447
  methods_str = ", ".join(auth_methods_tried) if auth_methods_tried else "none"
@@ -336,7 +336,7 @@ name = "exceptiongroup"
336
336
  version = "1.3.1"
337
337
  source = { registry = "https://pypi.org/simple" }
338
338
  dependencies = [
339
- { name = "typing-extensions", marker = "python_full_version < '3.12'" },
339
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
340
340
  ]
341
341
  sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
342
342
  wheels = [
@@ -345,7 +345,7 @@ wheels = [
345
345
 
346
346
  [[package]]
347
347
  name = "fujin-cli"
348
- version = "0.21.4"
348
+ version = "0.22.0"
349
349
  source = { editable = "." }
350
350
  dependencies = [
351
351
  { name = "cappa" },
@@ -401,7 +401,7 @@ docs = [
401
401
 
402
402
  [[package]]
403
403
  name = "fujin-secrets-1password"
404
- version = "0.21.4"
404
+ version = "0.22.0"
405
405
  source = { editable = "plugins/fujin-secrets-1password" }
406
406
  dependencies = [
407
407
  { name = "fujin-cli" },
@@ -416,7 +416,7 @@ requires-dist = [
416
416
 
417
417
  [[package]]
418
418
  name = "fujin-secrets-bitwarden"
419
- version = "0.21.4"
419
+ version = "0.22.0"
420
420
  source = { editable = "plugins/fujin-secrets-bitwarden" }
421
421
  dependencies = [
422
422
  { name = "fujin-cli" },
@@ -431,7 +431,7 @@ requires-dist = [
431
431
 
432
432
  [[package]]
433
433
  name = "fujin-secrets-doppler"
434
- version = "0.21.4"
434
+ version = "0.22.0"
435
435
  source = { editable = "plugins/fujin-secrets-doppler" }
436
436
  dependencies = [
437
437
  { name = "fujin-cli" },
@@ -1 +0,0 @@
1
- __version__ = "0.22.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes