Compare commits

..

No commits in common. "master" and "v1.3.0" have entirely different histories.

16 changed files with 1550 additions and 1687 deletions

View File

@ -1,7 +1,6 @@
version: 2 version: 2
enable-beta-ecosystems: true
updates: updates:
- package-ecosystem: uv - package-ecosystem: pip
directory: "/" directory: "/"
schedule: schedule:
interval: daily interval: daily

View File

@ -24,15 +24,23 @@ jobs:
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install uv - name: Run image
uses: astral-sh/setup-uv@v6 uses: abatilo/actions-poetry@v4
with: with:
version: "0.4.30" poetry-version: ${{ matrix.poetry-version }}
enable-cache: true - name: Setup a local virtual environment (if no poetry.toml file)
cache-dependency-glob: "uv.lock" run: |
- name: Install the project poetry config virtualenvs.create true --local
run: uv sync --all-extras --dev poetry config virtualenvs.in-project true --local
- uses: actions/cache@v4
name: Define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
key: venv-${{ hashFiles('poetry.lock') }}
- name: Install the project dependencies
run: poetry install
- name: Lint with flake8 - name: Lint with flake8
run: uv run flake8 sshuttle tests --count --show-source --statistics run: |
poetry run flake8 sshuttle tests --count --show-source --statistics
- name: Run the automated tests - name: Run the automated tests
run: uv run pytest -v run: poetry run pytest -v

View File

@ -12,44 +12,16 @@ jobs:
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps: steps:
- uses: googleapis/release-please-action@v4 - uses: googleapis/release-please-action@v4
id: release
with: with:
token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }} token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
release-type: python release-type: python
build-pypi:
name: Build for pypi
needs: [release-please]
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "0.4.30"
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Build project
run: uv build
- name: Store the distribution packages
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/
upload-pypi: upload-pypi:
name: Upload to pypi name: Upload to pypi
needs: [build-pypi] needs: [release-please]
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: environment:
name: pypi name: pypi
@ -57,10 +29,27 @@ jobs:
permissions: permissions:
id-token: write id-token: write
steps: steps:
- name: Download all the dists - uses: actions/checkout@v4
uses: actions/download-artifact@v4 - name: Set up Python 3.12
uses: actions/setup-python@v5
with: with:
name: python-package-distributions python-version: 3.12
path: dist/ - name: Run image
uses: abatilo/actions-poetry@v4
with:
poetry-version: main
- name: Setup a local virtual environment (if no poetry.toml file)
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- uses: actions/cache@v4
name: Define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
key: venv-${{ hashFiles('poetry.lock') }}
- name: Install the project dependencies
run: poetry install
- name: Package project
run: poetry build
- name: Publish package distributions to PyPI - name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@release/v1

View File

@ -5,9 +5,10 @@ build:
tools: tools:
python: "3.10" python: "3.10"
jobs: jobs:
post_create_environment:
- pip install poetry
post_install: post_install:
- pip install uv - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs
- UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --all-extras --group docs --link-mode=copy
sphinx: sphinx:
configuration: docs/conf.py configuration: docs/conf.py

View File

@ -1,15 +1,5 @@
# Changelog # Changelog
## [1.3.1](https://github.com/sshuttle/sshuttle/compare/v1.3.0...v1.3.1) (2025-03-25)
### Bug Fixes
* add pycodestyle config ([5942376](https://github.com/sshuttle/sshuttle/commit/5942376090395d0a8dfe38fe012a519268199341))
* add python lint tools ([ae3c022](https://github.com/sshuttle/sshuttle/commit/ae3c022d1d67de92f1c4712d06eb8ae76c970624))
* correct bad version number at runtime ([7b66253](https://github.com/sshuttle/sshuttle/commit/7b662536ba92d724ed8f86a32a21282fea66047c))
* Restore "nft" method ([375810a](https://github.com/sshuttle/sshuttle/commit/375810a9a8910a51db22c9fe4c0658c39b16c9e7))
## [1.3.0](https://github.com/sshuttle/sshuttle/compare/v1.2.0...v1.3.0) (2025-02-23) ## [1.3.0](https://github.com/sshuttle/sshuttle/compare/v1.2.0...v1.3.0) (2025-02-23)

View File

@ -16,7 +16,7 @@
import sys import sys
import os import os
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
import sshuttle # NOQA import sshuttle.version # NOQA
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
@ -56,7 +56,7 @@ copyright = '2016, Brian May'
# built documents. # built documents.
# #
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = sshuttle.__version__ release = sshuttle.version.version
# The short X.Y version. # The short X.Y version.
version = '.'.join(release.split('.')[:2]) version = '.'.join(release.split('.')[:2])

155
flake.lock generated
View File

@ -18,13 +18,52 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1740743217, "lastModified": 1738702386,
"narHash": "sha256-brsCRzLqimpyhORma84c3W2xPbIidZlIc3JGIuQVSNI=", "narHash": "sha256-nJj8f78AYAxl/zqLiFGXn5Im1qjFKU8yBPKoWEeZN5M=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b27ba4eb322d9d2bf2dc9ada9fd59442f50c8d7c", "rev": "030ba1976b7c0e1a67d9716b17308ccdab5b381e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -34,49 +73,27 @@
"type": "github" "type": "github"
} }
}, },
"pyproject-build-systems": { "poetry2nix": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"pyproject-nix": [ "systems": "systems_3",
"pyproject-nix" "treefmt-nix": "treefmt-nix"
],
"uv2nix": [
"uv2nix"
]
}, },
"locked": { "locked": {
"lastModified": 1740362541, "lastModified": 1738741221,
"narHash": "sha256-S8Mno07MspggOv/xIz5g8hB2b/C5HPiX8E+rXzKY+5U=", "narHash": "sha256-UiTOA89yQV5YNlO1ZAp4IqJUGWOnTyBC83netvt8rQE=",
"owner": "pyproject-nix", "owner": "nix-community",
"repo": "build-system-pkgs", "repo": "poetry2nix",
"rev": "e151741c848ba92331af91f4e47640a1fb82be19", "rev": "be1fe795035d3d36359ca9135b26dcc5321b31fb",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "pyproject-nix", "owner": "nix-community",
"repo": "build-system-pkgs", "repo": "poetry2nix",
"type": "github"
}
},
"pyproject-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1739758351,
"narHash": "sha256-Aoa4dEoC7Hf6+gFVk/SDquZTMFlmlfsgdTWuqQxzePs=",
"owner": "pyproject-nix",
"repo": "pyproject.nix",
"rev": "1329712f7f9af3a8b270764ba338a455b7323811",
"type": "github"
},
"original": {
"owner": "pyproject-nix",
"repo": "pyproject.nix",
"type": "github" "type": "github"
} }
}, },
@ -84,9 +101,7 @@
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"pyproject-build-systems": "pyproject-build-systems", "poetry2nix": "poetry2nix"
"pyproject-nix": "pyproject-nix",
"uv2nix": "uv2nix"
} }
}, },
"systems": { "systems": {
@ -104,26 +119,54 @@
"type": "github" "type": "github"
} }
}, },
"uv2nix": { "systems_2": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"pyproject-nix": [
"pyproject-nix"
]
},
"locked": { "locked": {
"lastModified": 1740497536, "lastModified": 1681028828,
"narHash": "sha256-K+8wsVooqhaqyxuvew3+62mgOfRLJ7whv7woqPU3Ypo=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "pyproject-nix", "owner": "nix-systems",
"repo": "uv2nix", "repo": "default",
"rev": "d01fd3a141755ad5d5b93dd9fcbd76d6401f5bac", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "pyproject-nix", "owner": "nix-systems",
"repo": "uv2nix", "repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1730120726,
"narHash": "sha256-LqHYIxMrl/1p3/kvm2ir925tZ8DkI0KA10djk8wecSk=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "9ef337e492a5555d8e17a51c911ff1f02635be15",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github" "type": "github"
} }
} }

119
flake.nix
View File

@ -1,24 +1,11 @@
{ {
description = "Transparent proxy server that works as a poor man's VPN. Forwards over ssh. Doesn't require admin. Works with Linux and MacOS. Supports DNS tunneling."; description = "Transparent proxy server that works as a poor man's VPN. Forwards over ssh. Doesn't require admin. Works with Linux and MacOS. Supports DNS tunneling.";
inputs = { inputs.flake-utils.url = "github:numtide/flake-utils";
flake-utils.url = "github:numtide/flake-utils"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; inputs.poetry2nix = {
pyproject-nix = { url = "github:nix-community/poetry2nix";
url = "github:pyproject-nix/pyproject.nix"; inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs.follows = "nixpkgs";
};
uv2nix = {
url = "github:pyproject-nix/uv2nix";
inputs.pyproject-nix.follows = "pyproject-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
pyproject-build-systems = {
url = "github:pyproject-nix/build-system-pkgs";
inputs.pyproject-nix.follows = "pyproject-nix";
inputs.uv2nix.follows = "uv2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = outputs =
@ -26,90 +13,42 @@
self, self,
nixpkgs, nixpkgs,
flake-utils, flake-utils,
pyproject-nix, poetry2nix,
uv2nix,
pyproject-build-systems,
}: }:
flake-utils.lib.eachDefaultSystem ( flake-utils.lib.eachDefaultSystem (
system: system:
let let
inherit (nixpkgs) lib; p2n = import poetry2nix { inherit pkgs; };
overrides = p2n.defaultPoetryOverrides.extend (
self: super: {
nh3 = super.nh3.override { preferWheel = true; };
bump2version = super.bump2version.overridePythonAttrs (old: {
buildInputs = (old.buildInputs or [ ]) ++ [ super.setuptools ];
});
}
);
poetry_env = p2n.mkPoetryEnv {
python = pkgs.python3;
projectDir = self;
inherit overrides;
};
poetry_app = p2n.mkPoetryApplication {
python = pkgs.python3;
projectDir = self;
inherit overrides;
};
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
python = pkgs.python312;
workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
# Create package overlay from workspace.
overlay = workspace.mkPyprojectOverlay {
sourcePreference = "sdist";
};
# Extend generated overlay with build fixups
#
# Uv2nix can only work with what it has, and uv.lock is missing essential metadata to perform some builds.
# This is an additional overlay implementing build fixups.
# See:
# - https://pyproject-nix.github.io/uv2nix/FAQ.html
pyprojectOverrides =
final: prev:
# Implement build fixups here.
# Note that uv2nix is _not_ using Nixpkgs buildPythonPackage.
# It's using https://pyproject-nix.github.io/pyproject.nix/build.html
let
inherit (final) resolveBuildSystem;
inherit (builtins) mapAttrs;
# Build system dependencies specified in the shape expected by resolveBuildSystem
# The empty lists below are lists of optional dependencies.
#
# A package `foo` with specification written as:
# `setuptools-scm[toml]` in pyproject.toml would be written as
# `foo.setuptools-scm = [ "toml" ]` in Nix
buildSystemOverrides = {
chardet.setuptools = [ ];
colorlog.setuptools = [ ];
python-debian.setuptools = [ ];
pluggy.setuptools = [ ];
pathspec.flit-core = [ ];
packaging.flit-core = [ ];
};
in
mapAttrs (
name: spec:
prev.${name}.overrideAttrs (old: {
nativeBuildInputs = old.nativeBuildInputs ++ resolveBuildSystem spec;
})
) buildSystemOverrides;
pythonSet =
(pkgs.callPackage pyproject-nix.build.packages {
inherit python;
}).overrideScope
(
lib.composeManyExtensions [
pyproject-build-systems.overlays.default
overlay
pyprojectOverrides
]
);
inherit (pkgs.callPackages pyproject-nix.build.util { }) mkApplication;
package = mkApplication {
venv = pythonSet.mkVirtualEnv "sshuttle" workspace.deps.default;
package = pythonSet.sshuttle;
};
in in
{ {
packages = { packages = {
sshuttle = package; sshuttle = poetry_app;
default = package; default = self.packages.${system}.sshuttle;
}; };
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
packages = [ packages = [
pkgs.uv pkgs.poetry
poetry_env
]; ];
}; };
} }

1330
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,9 @@
[project] [tool.poetry]
authors = [
{name = "Brian May", email = "brian@linuxpenguins.xyz"},
]
license = {text = "LGPL-2.1"}
requires-python = "<4.0,>=3.9"
dependencies = []
name = "sshuttle" name = "sshuttle"
version = "1.3.1" version = "1.3.0"
description = "Transparent proxy server that works as a poor man's VPN. Forwards over ssh. Doesn't require admin. Works with Linux and MacOS. Supports DNS tunneling." description = "Transparent proxy server that works as a poor man's VPN. Forwards over ssh. Doesn't require admin. Works with Linux and MacOS. Supports DNS tunneling."
authors = ["Brian May <brian@linuxpenguins.xyz>"]
license = "LGPL-2.1"
readme = "README.rst" readme = "README.rst"
classifiers = [ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
@ -22,36 +18,27 @@ classifiers = [
"Topic :: System :: Networking", "Topic :: System :: Networking",
] ]
[project.scripts] [tool.poetry.dependencies]
sshuttle = "sshuttle.cmdline:main" python = "^3.9"
[dependency-groups] [tool.poetry.group.dev.dependencies]
dev = [ pytest = "^8.0.1"
"pytest<9.0.0,>=8.0.1", pytest-cov = ">=4.1,<7.0"
"pytest-cov<7.0,>=4.1", flake8 = "^7.0.0"
"flake8<8.0.0,>=7.0.0", pyflakes = "^3.2.0"
"pyflakes<4.0.0,>=3.2.0", bump2version = "^1.0.1"
"bump2version<2.0.0,>=1.0.1", twine = ">=5,<7"
"twine<7,>=5",
"black>=25.1.0",
"jedi-language-server>=0.44.0",
"pylsp-mypy>=0.7.0",
"python-lsp-server>=1.12.2",
"ruff>=0.11.2",
]
docs = [
"sphinx==8.1.3; python_version ~= \"3.10\"",
"furo==2024.8.6",
]
[tool.uv]
default-groups = []
[build-system] [build-system]
requires = ["hatchling"] requires = ["poetry-core"]
build-backend = "hatchling.build" build-backend = "poetry.core.masonry.api"
[tool.hatch.build.targets.sdist] [tool.poetry.scripts]
exclude = [ sshuttle = "sshuttle.cmdline:main"
"/.jj"
] [tool.poetry.group.docs]
optional = true
[tool.poetry.group.docs.dependencies]
sphinx = { version = "8.1.3", python = ">=3.10,<4.0" }
furo = "2024.8.6"

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 1.3.1 current_version = 1.3.0
[bumpversion:file:setup.py] [bumpversion:file:setup.py]
@ -23,8 +23,5 @@ show-source = true
statistics = true statistics = true
max-line-length = 128 max-line-length = 128
[pycodestyle]
max-line-length = 128
[tool:pytest] [tool:pytest]
addopts = --cov=sshuttle --cov-branch --cov-report=term-missing addopts = --cov=sshuttle --cov-branch --cov-report=term-missing

View File

@ -1 +1,4 @@
__version__ = "1.3.1" try:
from sshuttle.version import version as __version__
except ImportError:
__version__ = "unknown"

View File

@ -247,7 +247,7 @@ parser.add_argument(
if sys.platform == 'win32': if sys.platform == 'win32':
method_choices = ["auto", "windivert"] method_choices = ["auto", "windivert"]
else: else:
method_choices = ["auto", "nft", "nat", "tproxy", "pf", "ipfw"] method_choices = ["auto", "nat", "tproxy", "pf", "ipfw"]
parser.add_argument( parser.add_argument(
"--method", "--method",

View File

@ -5,15 +5,7 @@ from uuid import uuid4
def build_config(user_name): def build_config(user_name):
"""Generates a sudoers configuration to allow passwordless execution of sshuttle.""" template = '''
argv0 = os.path.abspath(sys.argv[0])
is_python_script = argv0.endswith('.py')
executable = f"{sys.executable} {argv0}" if is_python_script else argv0
dist_packages = os.path.dirname(os.path.abspath(__file__))
cmd_alias = f"SSHUTTLE{uuid4().hex[-3:].upper()}"
template = f"""
# WARNING: If you intend to restrict a user to only running the # WARNING: If you intend to restrict a user to only running the
# sshuttle command as root, THIS CONFIGURATION IS INSECURE. # sshuttle command as root, THIS CONFIGURATION IS INSECURE.
# When a user can run sshuttle as root (with or without a password), # When a user can run sshuttle as root (with or without a password),
@ -24,18 +16,27 @@ def build_config(user_name):
# sshuttle without needing to enter a sudo password. To use this # sshuttle without needing to enter a sudo password. To use this
# configuration, run 'visudo /etc/sudoers.d/sshuttle_auto' as root and # configuration, run 'visudo /etc/sudoers.d/sshuttle_auto' as root and
# paste this text into the editor that it opens. If you want to give # paste this text into the editor that it opens. If you want to give
# multiple users these privileges, you may wish to use different # multiple users these privileges, you may wish to use use different
# filenames for each one (i.e., /etc/sudoers.d/sshuttle_auto_john). # filenames for each one (i.e., /etc/sudoers.d/sshuttle_auto_john).
# This configuration was initially generated by the # This configuration was initially generated by the
# 'sshuttle --sudoers-no-modify' command. # 'sshuttle --sudoers-no-modify' command.
Cmnd_Alias {cmd_alias} = /usr/bin/env PYTHONPATH={dist_packages} {executable} * Cmnd_Alias %(ca)s = /usr/bin/env PYTHONPATH=%(dist_packages)s %(py)s %(path)s *
{user_name} ALL=NOPASSWD: {cmd_alias} %(user_name)s ALL=NOPASSWD: %(ca)s
""" '''
return template content = template % {
# randomize command alias to avoid collisions
'ca': 'SSHUTTLE%(num)s' % {'num': uuid4().hex[-3:].upper()},
'dist_packages': os.path.dirname(os.path.abspath(__file__))[:-9],
'py': sys.executable,
'path': sys.argv[0],
'user_name': user_name,
}
return content
def sudoers(user_name=None): def sudoers(user_name=None):

1
sshuttle/version.py Normal file
View File

@ -0,0 +1 @@
__version__ = version = '1.2.0'

1425
uv.lock generated

File diff suppressed because it is too large Load Diff