Skip to content

Commit ee7cf95

Browse files
committed
Add local pypi registry Makefile targets and cli entrypoint
Signed-off-by: Mihai Criveti <[email protected]>
1 parent 71cd198 commit ee7cf95

File tree

4 files changed

+488
-0
lines changed

4 files changed

+488
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.htpasswd
12
.env.gcr
23
packages-lock.json
34
packages.json

Makefile

Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,3 +1437,379 @@ helm-deploy: helm-lint
14371437
helm-delete:
14381438
@echo "🗑 Deleting $(RELEASE_NAME) release..."
14391439
helm uninstall $(RELEASE_NAME) -n $(NAMESPACE) || true
1440+
1441+
# =============================================================================
1442+
# 🏠 LOCAL PYPI SERVER
1443+
# Currently blocked by: https://github.com/pypiserver/pypiserver/issues/630
1444+
# =============================================================================
1445+
# help: 🏠 LOCAL PYPI SERVER
1446+
# help: local-pypi-install - Install pypiserver for local testing
1447+
# help: local-pypi-start - Start local PyPI server on :8084 (no auth)
1448+
# help: local-pypi-start-auth - Start local PyPI server with basic auth (admin/admin)
1449+
# help: local-pypi-stop - Stop local PyPI server
1450+
# help: local-pypi-upload - Upload existing package to local PyPI (no auth)
1451+
# help: local-pypi-upload-auth - Upload existing package to local PyPI (with auth)
1452+
# help: local-pypi-test - Install package from local PyPI
1453+
# help: local-pypi-clean - Full cycle: build → upload → install locally
1454+
1455+
.PHONY: local-pypi-install local-pypi-start local-pypi-start-auth local-pypi-stop local-pypi-upload \
1456+
local-pypi-upload-auth local-pypi-test local-pypi-clean
1457+
1458+
LOCAL_PYPI_DIR := $(HOME)/local-pypi
1459+
LOCAL_PYPI_URL := http://localhost:8085
1460+
LOCAL_PYPI_PID := /tmp/pypiserver.pid
1461+
LOCAL_PYPI_AUTH := $(LOCAL_PYPI_DIR)/.htpasswd
1462+
1463+
local-pypi-install:
1464+
@echo "📦 Installing pypiserver..."
1465+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && pip install 'pypiserver>=2.3.0' passlib"
1466+
@mkdir -p $(LOCAL_PYPI_DIR)
1467+
1468+
local-pypi-start: local-pypi-install local-pypi-stop
1469+
@echo "🚀 Starting local PyPI server on http://localhost:8084..."
1470+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1471+
export PYPISERVER_BOTTLE_MEMFILE_MAX_OVERRIDE_BYTES=10485760 && \
1472+
pypi-server run -p 8084 -a . -P . $(LOCAL_PYPI_DIR) --hash-algo=sha256 & echo \$! > $(LOCAL_PYPI_PID)"
1473+
@sleep 2
1474+
@echo "✅ Local PyPI server started at http://localhost:8084"
1475+
@echo "📂 Package directory: $(LOCAL_PYPI_DIR)"
1476+
@echo "🔓 No authentication required (open mode)"
1477+
1478+
local-pypi-start-auth: local-pypi-install local-pypi-stop
1479+
@echo "🚀 Starting local PyPI server with authentication on $(LOCAL_PYPI_URL)..."
1480+
@echo "🔐 Creating htpasswd file (admin/admin)..."
1481+
@mkdir -p $(LOCAL_PYPI_DIR)
1482+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1483+
python3 -c \"import passlib.hash; print('admin:' + passlib.hash.sha256_crypt.hash('admin'))\" > $(LOCAL_PYPI_AUTH)"
1484+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1485+
export PYPISERVER_BOTTLE_MEMFILE_MAX_OVERRIDE_BYTES=10485760 && \
1486+
pypi-server run -p 8085 -P $(LOCAL_PYPI_AUTH) -a update,download,list $(LOCAL_PYPI_DIR) --hash-algo=sha256 & echo \$! > $(LOCAL_PYPI_PID)"
1487+
@sleep 2
1488+
@echo "✅ Local PyPI server started at $(LOCAL_PYPI_URL)"
1489+
@echo "📂 Package directory: $(LOCAL_PYPI_DIR)"
1490+
@echo "🔐 Username: admin, Password: admin"
1491+
1492+
local-pypi-stop:
1493+
@echo "🛑 Stopping local PyPI server..."
1494+
@if [ -f $(LOCAL_PYPI_PID) ]; then \
1495+
kill $(cat $(LOCAL_PYPI_PID)) 2>/dev/null || true; \
1496+
rm -f $(LOCAL_PYPI_PID); \
1497+
fi
1498+
@# Kill any pypi-server processes on ports 8084 and 8085
1499+
@pkill -f "pypi-server.*808[45]" 2>/dev/null || true
1500+
@# Wait a moment for cleanup
1501+
@sleep 1
1502+
@if lsof -i :8084 >/dev/null 2>&1; then \
1503+
echo "⚠️ Port 8084 still in use, force killing..."; \
1504+
sudo fuser -k 8084/tcp 2>/dev/null || true; \
1505+
fi
1506+
@if lsof -i :8085 >/dev/null 2>&1; then \
1507+
echo "⚠️ Port 8085 still in use, force killing..."; \
1508+
sudo fuser -k 8085/tcp 2>/dev/null || true; \
1509+
fi
1510+
@sleep 1
1511+
@echo "✅ Server stopped"
1512+
1513+
local-pypi-upload:
1514+
@echo "📤 Uploading existing package to local PyPI (no auth)..."
1515+
@if [ ! -d "dist" ] || [ -z "$$(ls -A dist/ 2>/dev/null)" ]; then \
1516+
echo "❌ No dist/ directory or files found. Run 'make dist' first."; \
1517+
exit 1; \
1518+
fi
1519+
@if ! curl -s http://localhost:8084 >/dev/null 2>&1; then \
1520+
echo "❌ Local PyPI server not running on port 8084. Run 'make local-pypi-start' first."; \
1521+
exit 1; \
1522+
fi
1523+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1524+
twine upload --verbose --repository-url http://localhost:8084 --skip-existing dist/*"
1525+
@echo "✅ Package uploaded to local PyPI"
1526+
@echo "🌐 Browse packages: http://localhost:8084"
1527+
1528+
local-pypi-upload-auth:
1529+
@echo "📤 Uploading existing package to local PyPI with auth..."
1530+
@if [ ! -d "dist" ] || [ -z "$$(ls -A dist/ 2>/dev/null)" ]; then \
1531+
echo "❌ No dist/ directory or files found. Run 'make dist' first."; \
1532+
exit 1; \
1533+
fi
1534+
@if ! curl -s $(LOCAL_PYPI_URL) >/dev/null 2>&1; then \
1535+
echo "❌ Local PyPI server not running on port 8085. Run 'make local-pypi-start-auth' first."; \
1536+
exit 1; \
1537+
fi
1538+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1539+
twine upload --verbose --repository-url $(LOCAL_PYPI_URL) --username admin --password admin --skip-existing dist/*"
1540+
@echo "✅ Package uploaded to local PyPI"
1541+
@echo "🌐 Browse packages: $(LOCAL_PYPI_URL)"
1542+
1543+
local-pypi-test:
1544+
@echo "📥 Installing from local PyPI..."
1545+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1546+
pip install --index-url $(LOCAL_PYPI_URL)/simple/ \
1547+
--extra-index-url https://pypi.org/simple/ \
1548+
--force-reinstall $(PROJECT_NAME)"
1549+
@echo "✅ Installed from local PyPI"
1550+
1551+
local-pypi-clean: clean dist local-pypi-start-auth local-pypi-upload-auth local-pypi-test
1552+
@echo "🎉 Full local PyPI cycle complete!"
1553+
@echo "📊 Package info:"
1554+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && pip show $(PROJECT_NAME)"
1555+
1556+
# Convenience target to restart server
1557+
local-pypi-restart: local-pypi-stop local-pypi-start
1558+
1559+
local-pypi-restart-auth: local-pypi-stop local-pypi-start-auth
1560+
1561+
# Show server status
1562+
local-pypi-status:
1563+
@echo "🔍 Local PyPI server status:"
1564+
@if [ -f $(LOCAL_PYPI_PID) ] && kill -0 $(cat $(LOCAL_PYPI_PID)) 2>/dev/null; then \
1565+
echo "✅ Server running (PID: $(cat $(LOCAL_PYPI_PID)))"; \
1566+
if curl -s http://localhost:8084 >/dev/null 2>&1; then \
1567+
echo "🌐 Server on port 8084: http://localhost:8084"; \
1568+
elif curl -s $(LOCAL_PYPI_URL) >/dev/null 2>&1; then \
1569+
echo "🌐 Server on port 8085: $(LOCAL_PYPI_URL)"; \
1570+
fi; \
1571+
echo "📂 Directory: $(LOCAL_PYPI_DIR)"; \
1572+
else \
1573+
echo "❌ Server not running"; \
1574+
fi
1575+
1576+
# Debug target - run server in foreground with verbose logging
1577+
local-pypi-debug:
1578+
@echo "🐛 Running local PyPI server in debug mode (Ctrl+C to stop)..."
1579+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1580+
export PYPISERVER_BOTTLE_MEMFILE_MAX_OVERRIDE_BYTES=10485760 && \
1581+
export BOTTLE_CHILD=true && \
1582+
pypi-server run -p 8085 --disable-fallback -a . -P . --server=auto $(LOCAL_PYPI_DIR) -v"
1583+
1584+
1585+
# =============================================================================
1586+
# 🏠 LOCAL DEVPI SERVER
1587+
# =============================================================================
1588+
# help: 🏠 LOCAL DEVPI SERVER
1589+
# help: devpi-install - Install devpi server and client
1590+
# help: devpi-init - Initialize devpi server (first time only)
1591+
# help: devpi-start - Start devpi server
1592+
# help: devpi-stop - Stop devpi server
1593+
# help: devpi-setup-user - Create user and dev index
1594+
# help: devpi-upload - Upload existing package to devpi
1595+
# help: devpi-test - Install package from devpi
1596+
# help: devpi-clean - Full cycle: build → upload → install locally
1597+
# help: devpi-status - Show devpi server status
1598+
# help: devpi-web - Open devpi web interface
1599+
1600+
.PHONY: devpi-install devpi-init devpi-start devpi-stop devpi-setup-user devpi-upload \
1601+
devpi-test devpi-clean devpi-status devpi-web devpi-restart
1602+
1603+
DEVPI_HOST := localhost
1604+
DEVPI_PORT := 3141
1605+
DEVPI_URL := http://$(DEVPI_HOST):$(DEVPI_PORT)
1606+
DEVPI_USER := $(USER)
1607+
DEVPI_PASS := dev123
1608+
DEVPI_INDEX := $(DEVPI_USER)/dev
1609+
DEVPI_DATA_DIR := $(HOME)/.devpi
1610+
DEVPI_PID := /tmp/devpi-server.pid
1611+
1612+
devpi-install:
1613+
@echo "📦 Installing devpi server and client..."
1614+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1615+
pip install devpi-server devpi-client devpi-web"
1616+
@echo "✅ DevPi installed"
1617+
1618+
devpi-init: devpi-install
1619+
@echo "🔧 Initializing devpi server (first time setup)..."
1620+
@if [ -d "$(DEVPI_DATA_DIR)/server" ] && [ -f "$(DEVPI_DATA_DIR)/server/.serverversion" ]; then \
1621+
echo "⚠️ DevPi already initialized at $(DEVPI_DATA_DIR)"; \
1622+
else \
1623+
mkdir -p $(DEVPI_DATA_DIR)/server; \
1624+
/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1625+
devpi-init --serverdir=$(DEVPI_DATA_DIR)/server"; \
1626+
echo "✅ DevPi server initialized at $(DEVPI_DATA_DIR)/server"; \
1627+
fi
1628+
1629+
devpi-start: devpi-init devpi-stop
1630+
@echo "🚀 Starting devpi server on $(DEVPI_URL)..."
1631+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1632+
devpi-server --serverdir=$(DEVPI_DATA_DIR)/server \
1633+
--host=$(DEVPI_HOST) \
1634+
--port=$(DEVPI_PORT) &"
1635+
@# Wait for server to start and get the PID
1636+
@sleep 3
1637+
@ps aux | grep "[d]evpi-server" | grep "$(DEVPI_PORT)" | awk '{print $2}' > $(DEVPI_PID) || true
1638+
@# Wait a bit more and test if server is responding
1639+
@sleep 2
1640+
@if curl -s $(DEVPI_URL) >/dev/null 2>&1; then \
1641+
if [ -s $(DEVPI_PID) ]; then \
1642+
echo "✅ DevPi server started at $(DEVPI_URL)"; \
1643+
echo "📊 PID: $(cat $(DEVPI_PID))"; \
1644+
else \
1645+
echo "✅ DevPi server started at $(DEVPI_URL)"; \
1646+
fi; \
1647+
echo "🌐 Web interface: $(DEVPI_URL)"; \
1648+
echo "📂 Data directory: $(DEVPI_DATA_DIR)"; \
1649+
else \
1650+
echo "❌ Failed to start devpi server or server not responding"; \
1651+
echo "🔍 Check logs with: make devpi-logs"; \
1652+
exit 1; \
1653+
fi
1654+
1655+
devpi-stop:
1656+
@echo "🛑 Stopping devpi server..."
1657+
@# Kill process by PID if exists
1658+
@if [ -f $(DEVPI_PID) ] && [ -s $(DEVPI_PID) ]; then \
1659+
pid=$(cat $(DEVPI_PID)); \
1660+
if kill -0 $pid 2>/dev/null; then \
1661+
echo "🔄 Stopping devpi server (PID: $pid)"; \
1662+
kill $pid 2>/dev/null || true; \
1663+
sleep 2; \
1664+
kill -9 $pid 2>/dev/null || true; \
1665+
fi; \
1666+
rm -f $(DEVPI_PID); \
1667+
fi
1668+
@# Kill any remaining devpi-server processes
1669+
@pids=$(pgrep -f "devpi-server.*$(DEVPI_PORT)" 2>/dev/null || true); \
1670+
if [ -n "$pids" ]; then \
1671+
echo "🔄 Killing remaining devpi processes: $pids"; \
1672+
echo "$pids" | xargs -r kill 2>/dev/null || true; \
1673+
sleep 1; \
1674+
echo "$pids" | xargs -r kill -9 2>/dev/null || true; \
1675+
fi
1676+
@# Force kill anything using the port
1677+
@if lsof -ti :$(DEVPI_PORT) >/dev/null 2>&1; then \
1678+
echo "⚠️ Port $(DEVPI_PORT) still in use, force killing..."; \
1679+
lsof -ti :$(DEVPI_PORT) | xargs -r kill -9 2>/dev/null || true; \
1680+
sleep 1; \
1681+
fi
1682+
@echo "✅ DevPi server stopped"
1683+
1684+
devpi-setup-user: devpi-start
1685+
@echo "👤 Setting up devpi user and index..."
1686+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1687+
devpi use $(DEVPI_URL) && \
1688+
(devpi user -c $(DEVPI_USER) password=$(DEVPI_PASS) email=$(DEVPI_USER)@localhost.local 2>/dev/null || \
1689+
echo 'User $(DEVPI_USER) already exists') && \
1690+
devpi login $(DEVPI_USER) --password=$(DEVPI_PASS) && \
1691+
(devpi index -c dev bases=root/pypi volatile=False 2>/dev/null || \
1692+
echo 'Index dev already exists') && \
1693+
devpi use $(DEVPI_INDEX)"
1694+
@echo "✅ User '$(DEVPI_USER)' and index 'dev' configured"
1695+
@echo "📝 Login: $(DEVPI_USER) / $(DEVPI_PASS)"
1696+
@echo "📍 Using index: $(DEVPI_INDEX)"
1697+
1698+
devpi-upload: devpi-setup-user
1699+
@echo "📤 Uploading existing package to devpi..."
1700+
@if [ ! -d "dist" ] || [ -z "$$(ls -A dist/ 2>/dev/null)" ]; then \
1701+
echo "❌ No dist/ directory or files found. Run 'make dist' first."; \
1702+
exit 1; \
1703+
fi
1704+
@if ! curl -s $(DEVPI_URL) >/dev/null 2>&1; then \
1705+
echo "❌ DevPi server not running. Run 'make devpi-start' first."; \
1706+
exit 1; \
1707+
fi
1708+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1709+
devpi use $(DEVPI_INDEX) && \
1710+
devpi upload dist/*"
1711+
@echo "✅ Package uploaded to devpi"
1712+
@echo "🌐 Browse packages: $(DEVPI_URL)/$(DEVPI_INDEX)"
1713+
1714+
devpi-test:
1715+
@echo "📥 Installing package from devpi..."
1716+
@if ! curl -s $(DEVPI_URL) >/dev/null 2>&1; then \
1717+
echo "❌ DevPi server not running. Run 'make devpi-start' first."; \
1718+
exit 1; \
1719+
fi
1720+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1721+
pip install --index-url $(DEVPI_URL)/$(DEVPI_INDEX)/+simple/ \
1722+
--extra-index-url https://pypi.org/simple/ \
1723+
--force-reinstall $(PROJECT_NAME)"
1724+
@echo "✅ Installed $(PROJECT_NAME) from devpi"
1725+
1726+
devpi-clean: clean dist devpi-upload devpi-test
1727+
@echo "🎉 Full devpi cycle complete!"
1728+
@echo "📊 Package info:"
1729+
@/bin/bash -c "source $(VENV_DIR)/bin/activate && pip show $(PROJECT_NAME)"
1730+
1731+
devpi-status:
1732+
@echo "🔍 DevPi server status:"
1733+
@if curl -s $(DEVPI_URL) >/dev/null 2>&1; then \
1734+
echo "✅ Server running at $(DEVPI_URL)"; \
1735+
if [ -f $(DEVPI_PID) ] && [ -s $(DEVPI_PID) ]; then \
1736+
echo "📊 PID: $$(cat $(DEVPI_PID))"; \
1737+
fi; \
1738+
echo "📂 Data directory: $(DEVPI_DATA_DIR)"; \
1739+
/bin/bash -c "source $(VENV_DIR)/bin/activate && \
1740+
devpi use $(DEVPI_URL) >/dev/null 2>&1 && \
1741+
devpi user --list 2>/dev/null || echo '📝 Not logged in'"; \
1742+
else \
1743+
echo "❌ Server not running"; \
1744+
fi
1745+
1746+
devpi-web:
1747+
@echo "🌐 Opening devpi web interface..."
1748+
@if curl -s $(DEVPI_URL) >/dev/null 2>&1; then \
1749+
echo "📱 Web interface: $(DEVPI_URL)"; \
1750+
which open >/dev/null 2>&1 && open $(DEVPI_URL) || \
1751+
which xdg-open >/dev/null 2>&1 && xdg-open $(DEVPI_URL) || \
1752+
echo "🔗 Open $(DEVPI_URL) in your browser"; \
1753+
else \
1754+
echo "❌ DevPi server not running. Run 'make devpi-start' first."; \
1755+
fi
1756+
1757+
devpi-restart: devpi-stop devpi-start
1758+
@echo "🔄 DevPi server restarted"
1759+
1760+
# Advanced targets for devpi management
1761+
devpi-reset: devpi-stop
1762+
@echo "⚠️ Resetting devpi server (this will delete all data)..."
1763+
@read -p "Are you sure? This will delete all packages and users [y/N]: " confirm; \
1764+
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
1765+
rm -rf $(DEVPI_DATA_DIR); \
1766+
echo "✅ DevPi data reset. Run 'make devpi-init' to reinitialize."; \
1767+
else \
1768+
echo "❌ Reset cancelled."; \
1769+
fi
1770+
1771+
devpi-backup:
1772+
@echo "💾 Backing up devpi data..."
1773+
@timestamp=$$(date +%Y%m%d-%H%M%S); \
1774+
backup_file="$(HOME)/devpi-backup-$$timestamp.tar.gz"; \
1775+
tar -czf "$$backup_file" -C $(HOME) .devpi 2>/dev/null && \
1776+
echo "✅ Backup created: $$backup_file" || \
1777+
echo "❌ Backup failed"
1778+
1779+
devpi-logs:
1780+
@echo "📋 DevPi server logs:"
1781+
@if [ -f "$(DEVPI_DATA_DIR)/server/devpi.log" ]; then \
1782+
tail -f "$(DEVPI_DATA_DIR)/server/devpi.log"; \
1783+
elif [ -f "$(DEVPI_DATA_DIR)/server/.xproc/devpi-server/xprocess.log" ]; then \
1784+
tail -f "$(DEVPI_DATA_DIR)/server/.xproc/devpi-server/xprocess.log"; \
1785+
elif [ -f "$(DEVPI_DATA_DIR)/server/devpi-server.log" ]; then \
1786+
tail -f "$(DEVPI_DATA_DIR)/server/devpi-server.log"; \
1787+
else \
1788+
echo "❌ No log file found. Checking if server is running..."; \
1789+
ps aux | grep "[d]evpi-server" || echo "Server not running"; \
1790+
echo "📂 Expected log location: $(DEVPI_DATA_DIR)/server/devpi.log"; \
1791+
fi
1792+
1793+
# Configuration helper - creates pip.conf for easy devpi usage
1794+
devpi-configure-pip:
1795+
@echo "⚙️ Configuring pip to use devpi by default..."
1796+
@mkdir -p $(HOME)/.pip
1797+
@echo "[global]" > $(HOME)/.pip/pip.conf
1798+
@echo "index-url = $(DEVPI_URL)/$(DEVPI_INDEX)/+simple/" >> $(HOME)/.pip/pip.conf
1799+
@echo "extra-index-url = https://pypi.org/simple/" >> $(HOME)/.pip/pip.conf
1800+
@echo "trusted-host = $(DEVPI_HOST)" >> $(HOME)/.pip/pip.conf
1801+
@echo "" >> $(HOME)/.pip/pip.conf
1802+
@echo "[search]" >> $(HOME)/.pip/pip.conf
1803+
@echo "index = $(DEVPI_URL)/$(DEVPI_INDEX)/" >> $(HOME)/.pip/pip.conf
1804+
@echo "✅ Pip configured to use devpi at $(DEVPI_URL)/$(DEVPI_INDEX)"
1805+
@echo "📝 Config file: $(HOME)/.pip/pip.conf"
1806+
1807+
# Remove pip devpi configuration
1808+
devpi-unconfigure-pip:
1809+
@echo "🔧 Removing devpi from pip configuration..."
1810+
@if [ -f "$(HOME)/.pip/pip.conf" ]; then \
1811+
rm "$(HOME)/.pip/pip.conf"; \
1812+
echo "✅ Pip configuration reset to defaults"; \
1813+
else \
1814+
echo "ℹ️ No pip configuration found"; \
1815+
fi

0 commit comments

Comments
 (0)