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.
- portmaster-1.0.0/.github/workflows/ci.yml +51 -0
- portmaster-1.0.0/.github/workflows/release.yml +52 -0
- portmaster-1.0.0/.gitignore +11 -0
- portmaster-1.0.0/AGENTS.md +5 -0
- portmaster-1.0.0/CHANGELOG.md +83 -0
- portmaster-1.0.0/CLAUDE.md +76 -0
- portmaster-1.0.0/LICENSE +21 -0
- portmaster-1.0.0/PKG-INFO +525 -0
- portmaster-1.0.0/README.md +477 -0
- portmaster-1.0.0/docs/pendientes.md +117 -0
- portmaster-1.0.0/docs/plan-siguiente.md +323 -0
- portmaster-1.0.0/docs/superpowers/specs/2026-07-29-autodeteccion-design.md +70 -0
- portmaster-1.0.0/portmaster/__init__.py +1 -0
- portmaster-1.0.0/portmaster/__main__.py +4 -0
- portmaster-1.0.0/portmaster/browse.py +95 -0
- portmaster-1.0.0/portmaster/cli.py +633 -0
- portmaster-1.0.0/portmaster/config.py +273 -0
- portmaster-1.0.0/portmaster/detect.py +674 -0
- portmaster-1.0.0/portmaster/docker.py +80 -0
- portmaster-1.0.0/portmaster/doctor.py +326 -0
- portmaster-1.0.0/portmaster/ports.py +366 -0
- portmaster-1.0.0/portmaster/registry.py +228 -0
- portmaster-1.0.0/portmaster/runner.py +557 -0
- portmaster-1.0.0/portmaster/server.py +961 -0
- portmaster-1.0.0/portmaster/web/app.css +1123 -0
- portmaster-1.0.0/portmaster/web/app.js +1185 -0
- portmaster-1.0.0/portmaster/web/index.html +235 -0
- portmaster-1.0.0/portmaster/web/tokens.css +94 -0
- portmaster-1.0.0/pyproject.toml +66 -0
- portmaster-1.0.0/stack.example.yaml +48 -0
- portmaster-1.0.0/tests/conftest.py +45 -0
- portmaster-1.0.0/tests/test_cli.py +526 -0
- portmaster-1.0.0/tests/test_config.py +197 -0
- portmaster-1.0.0/tests/test_detect.py +738 -0
- portmaster-1.0.0/tests/test_ports.py +224 -0
- portmaster-1.0.0/tests/test_runner.py +612 -0
- 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,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.
|
portmaster-1.0.0/LICENSE
ADDED
|
@@ -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.
|