portmaster 1.0.0__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 (37) hide show
  1. portmaster-1.0.0/.github/workflows/ci.yml +51 -0
  2. portmaster-1.0.0/.github/workflows/release.yml +52 -0
  3. portmaster-1.0.0/.gitignore +11 -0
  4. portmaster-1.0.0/AGENTS.md +5 -0
  5. portmaster-1.0.0/CHANGELOG.md +83 -0
  6. portmaster-1.0.0/CLAUDE.md +76 -0
  7. portmaster-1.0.0/LICENSE +21 -0
  8. portmaster-1.0.0/PKG-INFO +525 -0
  9. portmaster-1.0.0/README.md +477 -0
  10. portmaster-1.0.0/docs/pendientes.md +117 -0
  11. portmaster-1.0.0/docs/plan-siguiente.md +323 -0
  12. portmaster-1.0.0/docs/superpowers/specs/2026-07-29-autodeteccion-design.md +70 -0
  13. portmaster-1.0.0/portmaster/__init__.py +1 -0
  14. portmaster-1.0.0/portmaster/__main__.py +4 -0
  15. portmaster-1.0.0/portmaster/browse.py +95 -0
  16. portmaster-1.0.0/portmaster/cli.py +633 -0
  17. portmaster-1.0.0/portmaster/config.py +273 -0
  18. portmaster-1.0.0/portmaster/detect.py +674 -0
  19. portmaster-1.0.0/portmaster/docker.py +80 -0
  20. portmaster-1.0.0/portmaster/doctor.py +326 -0
  21. portmaster-1.0.0/portmaster/ports.py +366 -0
  22. portmaster-1.0.0/portmaster/registry.py +228 -0
  23. portmaster-1.0.0/portmaster/runner.py +557 -0
  24. portmaster-1.0.0/portmaster/server.py +961 -0
  25. portmaster-1.0.0/portmaster/web/app.css +1123 -0
  26. portmaster-1.0.0/portmaster/web/app.js +1185 -0
  27. portmaster-1.0.0/portmaster/web/index.html +235 -0
  28. portmaster-1.0.0/portmaster/web/tokens.css +94 -0
  29. portmaster-1.0.0/pyproject.toml +66 -0
  30. portmaster-1.0.0/stack.example.yaml +48 -0
  31. portmaster-1.0.0/tests/conftest.py +45 -0
  32. portmaster-1.0.0/tests/test_cli.py +526 -0
  33. portmaster-1.0.0/tests/test_config.py +197 -0
  34. portmaster-1.0.0/tests/test_detect.py +738 -0
  35. portmaster-1.0.0/tests/test_ports.py +224 -0
  36. portmaster-1.0.0/tests/test_runner.py +612 -0
  37. portmaster-1.0.0/tests/test_server.py +1098 -0
@@ -0,0 +1,51 @@
1
+ name: tests
2
+
3
+ # Por push y no por pull_request. El evento de PR dejo de producir runs en este
4
+ # repo el 6 de agosto: ni el push de un agente a una rama con PR abierto ni un
5
+ # PR nuevo dispararon nada, con Actions habilitado y el workflow activo. Por
6
+ # push cada rama corre sus tests igual, el PR muestra el check de su rama, y no
7
+ # hay dos runs por commit como pasaria dejando los dos eventos.
8
+ on:
9
+ push:
10
+ workflow_dispatch:
11
+
12
+ # Empujar tres veces seguidas cancela las corridas viejas en vez de encolar tres
13
+ # matrices de cuatro cuadros.
14
+ concurrency:
15
+ group: ${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ lint:
20
+ # Uno solo: ruff lee el codigo, no el sistema operativo. Repetirlo en los
21
+ # cuatro cuadros de la matriz no dice nada nuevo.
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.13"
28
+ - run: pip install ruff
29
+ - run: ruff check portmaster tests
30
+
31
+ test:
32
+ # El modulo de puertos habla directo con el SO y los tres sistemas divergen:
33
+ # SO_REUSEADDR en Windows, tabla de conexiones sin root en macOS. Correr en
34
+ # uno solo no prueba nada.
35
+ runs-on: ${{ matrix.os }}
36
+ strategy:
37
+ fail-fast: false
38
+ matrix:
39
+ os: [ubuntu-latest, macos-latest, windows-latest]
40
+ python: ["3.13"]
41
+ include:
42
+ - os: ubuntu-latest
43
+ python: "3.10"
44
+
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/setup-python@v5
48
+ with:
49
+ python-version: ${{ matrix.python }}
50
+ - run: pip install -e ".[dev]"
51
+ - run: pytest -q
@@ -0,0 +1,52 @@
1
+ name: release
2
+
3
+ # Se dispara con el tag, no con el push a main: publicar es irreversible en PyPI
4
+ # (un numero de version no se puede reusar ni despues de borrarlo) y merecer un
5
+ # gesto explicito.
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.13"
18
+
19
+ # El numero sale de portmaster/__init__.py, que es la unica fuente. Si el
20
+ # tag no coincide, se corta antes de publicar: un v1.0.1 que sube un
21
+ # paquete que se llama 1.0.0 no se puede deshacer.
22
+ - name: El tag tiene que coincidir con la version del paquete
23
+ run: |
24
+ tag="${GITHUB_REF_NAME#v}"
25
+ pkg=$(python -c "import re,pathlib; print(re.search(r'\"(.+)\"', pathlib.Path('portmaster/__init__.py').read_text()).group(1))")
26
+ echo "tag=$tag paquete=$pkg"
27
+ [ "$tag" = "$pkg" ]
28
+
29
+ - run: pip install build
30
+ - run: python -m build
31
+ - uses: actions/upload-artifact@v4
32
+ with:
33
+ name: dist
34
+ path: dist/
35
+
36
+ publish:
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ # El entorno permite exigir aprobacion manual desde la configuracion del
40
+ # repo antes de que corra este job.
41
+ environment: pypi
42
+ permissions:
43
+ # Trusted publishing: PyPI verifica este token de OIDC contra el publisher
44
+ # configurado para el repo. Sin API token en secrets, que es un secreto de
45
+ # larga vida que no caduca si se filtra.
46
+ id-token: write
47
+ steps:
48
+ - uses: actions/download-artifact@v4
49
+ with:
50
+ name: dist
51
+ path: dist/
52
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .env
10
+ .gstack/
11
+ .freebuff/
@@ -0,0 +1,5 @@
1
+ Las instrucciones del proyecto viven en [CLAUDE.md](CLAUDE.md).
2
+
3
+ Este archivo existe para los agentes que buscan `AGENTS.md` por convención. Era
4
+ una copia byte a byte de `CLAUDE.md`, y dos copias del mismo contrato divergen
5
+ en el primer cambio.
@@ -0,0 +1,83 @@
1
+ # Changelog
2
+
3
+ Formato de [Keep a Changelog](https://keepachangelog.com/es/1.1.0/).
4
+ Versionado semántico: la superficie pública son los comandos del CLI, el
5
+ esquema de `stack.yaml` y las rutas de la API local.
6
+
7
+ ## [1.0.0] - 2026-08-06
8
+
9
+ Primera versión publicada. Lo que sigue es el alcance completo, no un diff.
10
+
11
+ ### Orquestación
12
+
13
+ - `portmaster up` arranca los servicios en orden topológico, con los que no
14
+ dependen entre sí en paralelo, healthchecks por puerto, log o comando, y
15
+ apagado del árbol completo de procesos al salir.
16
+ - `portmaster down` baja un stack de compose puro, corriendo los `stop:` en
17
+ orden inverso.
18
+ - `portmaster switch` baja los proyectos registrados que le pisan los puertos a
19
+ uno y lo levanta, sin tocar los que no compiten.
20
+ - `stack.yaml` declara servicios, dependencias, puertos, healthchecks, perfiles
21
+ y variables. Sin archivo, `detect` infiere el stack de compose, Django,
22
+ FastAPI, Node, Go, Rust, Rails, Laravel y ASP.NET Core, en la raíz o en
23
+ subcarpetas.
24
+ - `portmaster init` congela lo detectado a un `stack.yaml` editable.
25
+
26
+ ### Puertos
27
+
28
+ - `portmaster ports` y `portmaster free` escanean, identifican al proceso dueño
29
+ y lo cierran con verificación de `create_time` contra el reciclado de PIDs.
30
+ - `portmaster free --all` recorre los puertos de todos los proyectos
31
+ registrados y cierra lo que los ocupe, con una sola confirmación que lista
32
+ antes qué va a cerrar.
33
+ - El cierre se niega sobre el proceso propio, sobre los PIDs de sistema, y
34
+ sobre el proxy compartido de Docker o WSL, porque cerrar ese proxy apaga el
35
+ motor entero con todos sus contenedores.
36
+ - Cuando un healthcheck se agota, el error dice si el servicio abrió otro
37
+ puerto que el declarado, o quién tenía el declarado.
38
+
39
+ ### Interfaz web
40
+
41
+ - `portmaster serve` levanta una interfaz local en `127.0.0.1:7666` para
42
+ arrancar, apagar y reiniciar servicios de varios proyectos, con logs
43
+ incrementales, explorador de carpetas, buscador, paginado, filtro por estado
44
+ y sección de procesos intrusos.
45
+ - La tarjeta marca al lado del puerto cuando otro proyecto registrado declara
46
+ ese mismo puerto, y cuando el puerto ya estaba ocupado antes de arrancar.
47
+ - "Liberar todos" cierra de una todos los procesos intrusos, en dos pasos sobre
48
+ el mismo botón: el segundo nombra puertos y procesos antes de hacerlo.
49
+ - Estado de Docker en la fila de herramientas cuando algún proyecto lo usa, con
50
+ un botón que abre el motor si está caído y lo reinicia si está arriba.
51
+ Reiniciar pide confirmación: baja todos los contenedores.
52
+ - La sección de procesos intrusos ya no desaparece cuando no hay ninguno: lo
53
+ dice.
54
+ - Sin build y sin webfonts: la CSP es `default-src 'self'`.
55
+
56
+ ### Diagnóstico
57
+
58
+ - `portmaster doctor` junta los chequeos en una salida, cada rojo con la línea
59
+ que lo arregla. Funciona en una carpeta que no es un proyecto conocido.
60
+ - Compara las claves de `.env.example` contra el `.env` y avisa las que faltan
61
+ o quedaron vacías. Nombres, nunca valores.
62
+
63
+ ### Seguridad
64
+
65
+ - Token de 32 bytes en `~/.portmaster/token` con permisos 0600, o
66
+ `PORTMASTER_TOKEN`. El servidor no arranca sin token.
67
+ - Validación del header `Host` contra rebinding de DNS, antes de cualquier otra
68
+ cosa.
69
+ - CSP, `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy` y
70
+ `Cache-Control: no-store` en toda respuesta. Sin `Strict-Transport-Security`:
71
+ el servidor es http sobre loopback y el header rompería cualquier otro
72
+ proyecto local.
73
+ - Rate limiting por ruta: 1800 lecturas, 60 escrituras y 30 cierres de proceso
74
+ por ventana.
75
+
76
+ ### Conocido
77
+
78
+ - Con `ready: port`, un proceso ajeno que ya tenga el puerto declarado hace que
79
+ el servicio figure listo al instante. El arranque lo avisa, por consola y en
80
+ la tarjeta, y no lo impide: `docker compose up -d` sobre un contenedor que ya
81
+ está arriba es el mismo caso y ahí es correcto. Ver `docs/pendientes.md`.
82
+
83
+ [1.0.0]: https://github.com/TicoraX/PortMaster/releases/tag/v1.0.0
@@ -0,0 +1,76 @@
1
+ # PortMaster
2
+
3
+ Herramienta de consola en Python que orquesta entornos de desarrollo locales:
4
+ libera puertos, arranca Docker, backend y frontend en orden, y expone una
5
+ interfaz web local para gestionar varios proyectos.
6
+
7
+ ## Estructura
8
+
9
+ | Modulo | Responsabilidad |
10
+ |---|---|
11
+ | `ports.py` | Escaneo de puertos, identificacion del proceso dueno, cierre seguro |
12
+ | `config.py` | Carga y validacion de `stack.yaml` |
13
+ | `detect.py` | Servicios inferidos del proyecto cuando no hay `stack.yaml` |
14
+ | `runner.py` | Arranque en orden topologico, healthchecks, apagado del arbol |
15
+ | `registry.py` | Proyectos conocidos por la interfaz, y el token de la API |
16
+ | `docker.py` | Arranque y reinicio de Docker Desktop. Diagnosticar si esta arriba es de `doctor.py` |
17
+ | `server.py` | API local en FastAPI |
18
+ | `web/` | Interfaz: HTML, CSS y JS sin build |
19
+ | `cli.py` | Comandos Typer |
20
+
21
+ El core no depende de la terminal. El CLI y el servidor son dos consumidores de
22
+ las mismas funciones; cualquier logica nueva va en el modulo correspondiente, no
23
+ en `cli.py` ni en `server.py`.
24
+
25
+ ## Desviaciones deliberadas de las reglas globales
26
+
27
+ Cada una con su motivo. No revertirlas sin leerlo primero.
28
+
29
+ ### El token no vive en un `.env`
30
+
31
+ La regla pide secretos en `.env` con validacion al arrancar. PortMaster se
32
+ instala con `pipx` o `uv tool`: no hay repositorio donde poner un `.env`, y
33
+ pedirle al usuario que genere y copie un token a mano garantiza que termine
34
+ usando `token=1234`.
35
+
36
+ En su lugar: `PORTMASTER_TOKEN` tiene prioridad si existe, y si no,
37
+ `registry.token()` genera uno de 32 bytes en `~/.portmaster/token` con permisos
38
+ 0600. Se valida largo minimo de 16 en ambos casos y el servidor no arranca sin
39
+ token. El secreto queda fuera del repo, que es lo que la regla protege.
40
+
41
+ ### Las cuotas de rate limit no son las de referencia
42
+
43
+ La referencia es 100/15min general. La interfaz sondea `/api/state` cada 2.5s,
44
+ que da unas 360 peticiones por ventana: con 100 se rompe el uso normal. Las
45
+ cuotas quedaron en `QUOTA_READ = 1800`, `QUOTA_WRITE = 60`, `QUOTA_KILL = 30`.
46
+ Las rutas que ejecutan comandos o matan procesos son las que van cortas, que es
47
+ donde la regla importa.
48
+
49
+ ### Sin `Strict-Transport-Security`
50
+
51
+ El servidor es http sobre loopback. Ese header le impondria https a todo
52
+ `127.0.0.1` en el navegador del usuario y romperia cualquier otro proyecto local
53
+ que corra en http. Los otros headers (CSP, nosniff, DENY, no-referrer) si estan.
54
+
55
+ En su lugar, contra rebinding de DNS: validacion del header `Host` contra
56
+ `127.0.0.1` y `localhost`, antes de cualquier otra cosa.
57
+
58
+ ### `shell=True` en los subprocesos
59
+
60
+ `npm run dev` y `docker compose up -d` no son ejecutables. `stack.yaml` ya es
61
+ codigo ejecutable por diseno, igual que `package.json` o un `Makefile`, y el
62
+ README lo documenta. Por eso el apagado mata el arbol de procesos completo: con
63
+ `shell=True` el hijo directo es el shell y matarlo solo a el deja huerfano al
64
+ servidor de verdad.
65
+
66
+ ### La interfaz no usa webfonts
67
+
68
+ La CSP es `default-src 'self'`. Una herramienta local que le pide fuentes a un
69
+ CDN en cada carga filtra actividad y deja de funcionar sin internet. Los tres
70
+ roles tipograficos salen de stacks del sistema.
71
+
72
+ ## Tests
73
+
74
+ `pytest`. Todo con sockets y procesos reales, sin mocks: es la unica forma de
75
+ probar un modulo cuyo trabajo es hablar con el sistema operativo. La CI corre en
76
+ Linux, macOS y Windows porque los tres divergen justo ahi.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TicoraX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.