diff --git a/.bashrc b/.bashrc index 5bddb37..32e8bf2 100644 --- a/.bashrc +++ b/.bashrc @@ -70,292 +70,6 @@ alias gcb='git checkout -b' alias kubectl="minikube kubectl --" alias zed=zeditor -# Check if a specific port is in use with detailed process information -inuse() { - # Color definitions - local RED='\033[0;31m' - local GREEN='\033[0;32m' - local YELLOW='\033[1;33m' - local BLUE='\033[0;34m' - local CYAN='\033[0;36m' - local BOLD='\033[1m' - local NC='\033[0m' # No Color - - # Input validation - if [ $# -eq 0 ]; then - echo -e "${RED}Usage:${NC} inuse " - echo -e "${YELLOW} inuse --list${NC}" - echo -e "${YELLOW} inuse --help${NC}" - echo -e "${YELLOW}Example:${NC} inuse 80" - echo -e "${YELLOW} inuse --list${NC}" - return 1 - fi - - # Handle --help option - if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then - echo -e "${CYAN}${BOLD}inuse - Check if a port is in use${NC}" - echo - echo -e "${BOLD}USAGE:${NC}" - echo -e " inuse Check if a specific port is in use" - echo -e " inuse --list, -l List all Docker services with listening ports" - echo -e " inuse --help, -h Show this help message" - echo - echo -e "${BOLD}EXAMPLES:${NC}" - echo -e " ${GREEN}inuse 80${NC} Check if port 80 is in use" - echo -e " ${GREEN}inuse 3000${NC} Check if port 3000 is in use" - echo -e " ${GREEN}inuse --list${NC} Show all Docker services with ports" - echo - echo -e "${BOLD}DESCRIPTION:${NC}" - echo -e " The inuse function checks if a specific port is in use and identifies" - echo -e " the process using it. It can detect regular processes, Docker containers" - echo -e " with published ports, and containers using host networking." - echo - echo -e "${BOLD}OUTPUT:${NC}" - echo -e " ${GREEN}āœ“${NC} Port is in use - shows process name, PID, and Docker info if applicable" - echo -e " ${RED}āœ—${NC} Port is free" - echo -e " ${YELLOW}⚠${NC} Port is in use but process cannot be identified" - echo - return 0 - fi - - # Handle --list option - if [ "$1" = "--list" ] || [ "$1" = "-l" ]; then - if ! command -v docker >/dev/null 2>&1; then - echo -e "${RED}Error:${NC} Docker is not available" - return 1 - fi - - echo -e "${CYAN}${BOLD}Docker Services with Listening Ports:${NC}" - echo - - # Get all running containers - local containers=$(docker ps --format "{{.Names}}" 2>/dev/null) - if [ -z "$containers" ]; then - echo -e "${YELLOW}No running Docker containers found${NC}" - return 0 - fi - - local found_services=false - while IFS= read -r container; do - # Get port mappings for this container - local ports=$(docker port "$container" 2>/dev/null) - if [ -n "$ports" ]; then - # Get container image name (clean it up) - local image=$(docker inspect "$container" 2>/dev/null | grep -o '"Image": *"[^"]*"' | cut -d'"' -f4 | head -1) - local clean_image=$(echo "$image" | sed 's/sha256:[a-f0-9]*/[image-hash]/' | sed 's/^.*\///') - - echo -e "${GREEN}šŸ“¦ ${BOLD}$container${NC} ${CYAN}($clean_image)${NC}" - - # Parse and display ports nicely - echo "$ports" | while IFS= read -r port_line; do - if [[ "$port_line" =~ ([0-9]+)/(tcp|udp).*0\.0\.0\.0:([0-9]+) ]]; then - local container_port="${BASH_REMATCH[1]}" - local protocol="${BASH_REMATCH[2]}" - local host_port="${BASH_REMATCH[3]}" - echo -e "${CYAN} ā”œā”€ Port ${BOLD}$host_port${NC}${CYAN} → $container_port ($protocol)${NC}" - elif [[ "$port_line" =~ ([0-9]+)/(tcp|udp).*\[::\]:([0-9]+) ]]; then - local container_port="${BASH_REMATCH[1]}" - local protocol="${BASH_REMATCH[2]}" - local host_port="${BASH_REMATCH[3]}" - echo -e "${CYAN} ā”œā”€ Port ${BOLD}$host_port${NC}${CYAN} → $container_port ($protocol) [IPv6]${NC}" - fi - done - echo - found_services=true - fi - done <<< "$containers" - - # Also check for host networking containers - local host_containers=$(docker ps --format "{{.Names}}" --filter "network=host" 2>/dev/null) - if [ -n "$host_containers" ]; then - echo -e "${YELLOW}${BOLD}Host Networking Containers:${NC}" - while IFS= read -r container; do - local image=$(docker inspect "$container" 2>/dev/null | grep -o '"Image": *"[^"]*"' | cut -d'"' -f4 | head -1) - local clean_image=$(echo "$image" | sed 's/sha256:[a-f0-9]*/[image-hash]/' | sed 's/^.*\///') - echo -e "${YELLOW}🌐 ${BOLD}$container${NC} ${CYAN}($clean_image)${NC} ${YELLOW}- uses host networking${NC}" - done <<< "$host_containers" - echo - found_services=true - fi - - if [ "$found_services" = false ]; then - echo -e "${YELLOW}No Docker services with exposed ports found${NC}" - fi - - return 0 - fi - - local port="$1" - - # Validate port number - if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then - echo -e "${RED}Error:${NC} Invalid port number. Must be between 1 and 65535." - return 1 - fi - - # Check if port is in use first - local port_in_use=false - if command -v ss >/dev/null 2>&1; then - if ss -tulpn 2>/dev/null | grep -q ":$port "; then - port_in_use=true - fi - elif command -v netstat >/dev/null 2>&1; then - if netstat -tulpn 2>/dev/null | grep -q ":$port "; then - port_in_use=true - fi - fi - - if [ "$port_in_use" = false ]; then - echo -e "${RED}āœ— Port $port is FREE${NC}" - return 1 - fi - - # Port is in use, now find what's using it - local found_process=false - - # Method 1: Try netstat first (most reliable for PID info) - if command -v netstat >/dev/null 2>&1; then - local netstat_result=$(netstat -tulpn 2>/dev/null | grep ":$port ") - if [ -n "$netstat_result" ]; then - while IFS= read -r line; do - local pid=$(echo "$line" | awk '{print $7}' | cut -d'/' -f1) - local process_name=$(echo "$line" | awk '{print $7}' | cut -d'/' -f2) - local protocol=$(echo "$line" | awk '{print $1}') - - if [[ "$pid" =~ ^[0-9]+$ ]] && [ -n "$process_name" ]; then - # Check if it's a Docker container - local docker_info="" - if command -v docker >/dev/null 2>&1; then - # Check for docker-proxy - if [ "$process_name" = "docker-proxy" ]; then - local container_name=$(docker ps --format "{{.Names}}" --filter "publish=$port" 2>/dev/null | head -1) - if [ -n "$container_name" ]; then - docker_info=" ${CYAN}(Docker: $container_name)${NC}" - else - docker_info=" ${CYAN}(Docker proxy)${NC}" - fi - else - # Check if process is in a container by examining cgroup - if [ -f "/proc/$pid/cgroup" ] && grep -q docker "/proc/$pid/cgroup" 2>/dev/null; then - local container_id=$(cat "/proc/$pid/cgroup" 2>/dev/null | grep docker | grep -o '[a-f0-9]\{64\}' | head -1) - if [ -n "$container_id" ]; then - local container_name=$(docker inspect "$container_id" 2>/dev/null | grep -o '"Name": *"[^"]*"' | cut -d'"' -f4 | sed 's/^\/*//' | head -1) - if [ -n "$container_name" ]; then - docker_info=" ${CYAN}(Docker: $container_name)${NC}" - else - docker_info=" ${CYAN}(Docker: ${container_id:0:12})${NC}" - fi - fi - fi - fi - fi - - echo -e "${GREEN}āœ“ Port $port ($protocol) in use by ${BOLD}$process_name${NC} ${GREEN}as PID ${BOLD}$pid${NC}$docker_info" - found_process=true - fi - done <<< "$netstat_result" - fi - fi - - # Method 2: Try ss if netstat didn't work - if [ "$found_process" = false ] && command -v ss >/dev/null 2>&1; then - local ss_result=$(ss -tulpn 2>/dev/null | grep ":$port ") - if [ -n "$ss_result" ]; then - while IFS= read -r line; do - local pid=$(echo "$line" | grep -o 'pid=[0-9]*' | cut -d'=' -f2) - local protocol=$(echo "$line" | awk '{print $1}') - - if [[ "$pid" =~ ^[0-9]+$ ]]; then - local process_name=$(ps -p "$pid" -o comm= 2>/dev/null) - if [ -n "$process_name" ]; then - # Check for Docker container - local docker_info="" - if command -v docker >/dev/null 2>&1; then - if [ "$process_name" = "docker-proxy" ]; then - local container_name=$(docker ps --format "{{.Names}}" --filter "publish=$port" 2>/dev/null | head -1) - if [ -n "$container_name" ]; then - docker_info=" ${CYAN}(Docker: $container_name)${NC}" - else - docker_info=" ${CYAN}(Docker proxy)${NC}" - fi - elif [ -f "/proc/$pid/cgroup" ] && grep -q docker "/proc/$pid/cgroup" 2>/dev/null; then - local container_id=$(cat "/proc/$pid/cgroup" 2>/dev/null | grep docker | grep -o '[a-f0-9]\{64\}' | head -1) - if [ -n "$container_id" ]; then - local container_name=$(docker inspect "$container_id" 2>/dev/null | grep -o '"Name": *"[^"]*"' | cut -d'"' -f4 | sed 's/^\/*//' | head -1) - if [ -n "$container_name" ]; then - docker_info=" ${CYAN}(Docker: $container_name)${NC}" - else - docker_info=" ${CYAN}(Docker: ${container_id:0:12})${NC}" - fi - fi - fi - fi - - echo -e "${GREEN}āœ“ Port $port ($protocol) in use by ${BOLD}$process_name${NC} ${GREEN}as PID ${BOLD}$pid${NC}$docker_info" - found_process=true - fi - fi - done <<< "$ss_result" - fi - fi - - # Method 3: Try fuser as last resort - if [ "$found_process" = false ] && command -v fuser >/dev/null 2>&1; then - local fuser_pids=$(fuser "$port/tcp" 2>/dev/null) - if [ -n "$fuser_pids" ]; then - for pid in $fuser_pids; do - if [[ "$pid" =~ ^[0-9]+$ ]]; then - local process_name=$(ps -p "$pid" -o comm= 2>/dev/null) - if [ -n "$process_name" ]; then - echo -e "${GREEN}āœ“ Port $port (tcp) in use by ${BOLD}$process_name${NC} ${GREEN}as PID ${BOLD}$pid${NC}" - found_process=true - break - fi - fi - done - fi - fi - - # Method 4: Check for Docker containers more accurately - if [ "$found_process" = false ] && command -v docker >/dev/null 2>&1; then - # First, try to find containers with published ports matching our port - local container_with_port=$(docker ps --format "{{.Names}}" --filter "publish=$port" 2>/dev/null | head -1) - if [ -n "$container_with_port" ]; then - local image=$(docker inspect "$container_with_port" 2>/dev/null | grep -o '"Image": *"[^"]*"' | cut -d'"' -f4 | head -1) - echo -e "${GREEN}āœ“ Port $port in use by Docker container ${BOLD}$container_with_port${NC} ${CYAN}(published port, image: $image)${NC}" - found_process=true - else - # Only check host networking containers if we haven't found anything else - local host_containers=$(docker ps --format "{{.Names}}" --filter "network=host" 2>/dev/null) - if [ -n "$host_containers" ]; then - local host_container_count=$(echo "$host_containers" | wc -l) - if [ "$host_container_count" -eq 1 ]; then - # Only one host networking container, likely candidate - local image=$(docker inspect "$host_containers" 2>/dev/null | grep -o '"Image": *"[^"]*"' | cut -d'"' -f4 | head -1) - echo -e "${YELLOW}⚠ Port $port possibly in use by Docker container ${BOLD}$host_containers${NC} ${CYAN}(host networking, image: $image)${NC}" - found_process=true - else - # Multiple host networking containers, can't determine which one - echo -e "${YELLOW}⚠ Port $port is in use, multiple Docker containers using host networking:${NC}" - while IFS= read -r container; do - local image=$(docker inspect "$container" 2>/dev/null | grep -o '"Image": *"[^"]*"' | cut -d'"' -f4 | head -1) - echo -e "${CYAN} - $container (image: $image)${NC}" - done <<< "$host_containers" - found_process=true - fi - fi - fi - fi - - # If we still haven't found the process, show a generic message - if [ "$found_process" = false ]; then - echo -e "${YELLOW}⚠ Port $port is in use but unable to identify the process${NC}" - echo -e "${CYAN} This might be due to insufficient permissions or the process being in a different namespace${NC}" - fi - - return 0 -} - # random string (Syntax: random ) alias random='openssl rand -base64' diff --git a/config/ansible/tasks/global/utils/inuse.go b/config/ansible/tasks/global/utils/inuse.go new file mode 100644 index 0000000..522bdb5 --- /dev/null +++ b/config/ansible/tasks/global/utils/inuse.go @@ -0,0 +1,748 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "os/exec" + "regexp" + "strconv" + "strings" +) + +// Color constants for terminal output +const ( + Red = "\033[0;31m" + Green = "\033[0;32m" + Yellow = "\033[1;33m" + Blue = "\033[0;34m" + Cyan = "\033[0;36m" + Bold = "\033[1m" + NC = "\033[0m" // No Color +) + +// ProcessInfo holds information about a process using a port +type ProcessInfo struct { + PID int + ProcessName string + Protocol string + DockerInfo string +} + +// DockerContainer represents a Docker container +type DockerContainer struct { + Name string + Image string + Ports []PortMapping + Network string +} + +// PortMapping represents a port mapping +type PortMapping struct { + ContainerPort int + HostPort int + Protocol string + IPv6 bool +} + +func main() { + if len(os.Args) < 2 { + showUsage() + os.Exit(1) + } + + arg := os.Args[1] + + switch arg { + case "--help", "-h": + showHelp() + case "--list", "-l": + listDockerServices() + default: + port, err := strconv.Atoi(arg) + if err != nil || port < 1 || port > 65535 { + fmt.Printf("%sError:%s Invalid port number. Must be between 1 and 65535.\n", Red, NC) + os.Exit(1) + } + checkPort(port) + } +} + +func showUsage() { + fmt.Printf("%sUsage:%s inuse \n", Red, NC) + fmt.Printf("%s inuse --list%s\n", Yellow, NC) + fmt.Printf("%s inuse --help%s\n", Yellow, NC) + fmt.Printf("%sExample:%s inuse 80\n", Yellow, NC) + fmt.Printf("%s inuse --list%s\n", Yellow, NC) +} + +func showHelp() { + fmt.Printf("%s%sinuse - Check if a port is in use%s\n\n", Cyan, Bold, NC) + fmt.Printf("%sUSAGE:%s\n", Bold, NC) + fmt.Printf(" inuse Check if a specific port is in use\n") + fmt.Printf(" inuse --list, -l List all Docker services with listening ports\n") + fmt.Printf(" inuse --help, -h Show this help message\n\n") + fmt.Printf("%sEXAMPLES:%s\n", Bold, NC) + fmt.Printf(" %sinuse 80%s Check if port 80 is in use\n", Green, NC) + fmt.Printf(" %sinuse 3000%s Check if port 3000 is in use\n", Green, NC) + fmt.Printf(" %sinuse --list%s Show all Docker services with ports\n\n", Green, NC) + fmt.Printf("%sDESCRIPTION:%s\n", Bold, NC) + fmt.Printf(" The inuse function checks if a specific port is in use and identifies\n") + fmt.Printf(" the process using it. It can detect regular processes, Docker containers\n") + fmt.Printf(" with published ports, and containers using host networking.\n\n") + fmt.Printf("%sOUTPUT:%s\n", Bold, NC) + fmt.Printf(" %sāœ“%s Port is in use - shows process name, PID, and Docker info if applicable\n", Green, NC) + fmt.Printf(" %sāœ—%s Port is free\n", Red, NC) + fmt.Printf(" %s⚠%s Port is in use but process cannot be identified\n", Yellow, NC) +} + +func listDockerServices() { + if !isDockerAvailable() { + fmt.Printf("%sError:%s Docker is not available\n", Red, NC) + os.Exit(1) + } + + fmt.Printf("%s%sDocker Services with Listening Ports:%s\n\n", Cyan, Bold, NC) + + containers := getRunningContainers() + if len(containers) == 0 { + fmt.Printf("%sNo running Docker containers found%s\n", Yellow, NC) + return + } + + foundServices := false + for _, container := range containers { + if len(container.Ports) > 0 { + cleanImage := cleanImageName(container.Image) + fmt.Printf("%sšŸ“¦ %s%s%s %s(%s)%s\n", Green, Bold, container.Name, NC, Cyan, cleanImage, NC) + + for _, port := range container.Ports { + ipv6Marker := "" + if port.IPv6 { + ipv6Marker = " [IPv6]" + } + fmt.Printf("%s ā”œā”€ Port %s%d%s%s → %d (%s)%s%s\n", + Cyan, Bold, port.HostPort, NC, Cyan, port.ContainerPort, port.Protocol, ipv6Marker, NC) + } + fmt.Println() + foundServices = true + } + } + + // Check for host networking containers + hostContainers := getHostNetworkingContainers() + if len(hostContainers) > 0 { + fmt.Printf("%s%sHost Networking Containers:%s\n", Yellow, Bold, NC) + for _, container := range hostContainers { + cleanImage := cleanImageName(container.Image) + fmt.Printf("%s🌐 %s%s%s %s(%s)%s %s- uses host networking%s\n", + Yellow, Bold, container.Name, NC, Cyan, cleanImage, NC, Yellow, NC) + } + fmt.Println() + foundServices = true + } + + if !foundServices { + fmt.Printf("%sNo Docker services with exposed ports found%s\n", Yellow, NC) + } +} + +func checkPort(port int) { + // Check if port is in use first + if !isPortInUse(port) { + fmt.Printf("%sāœ— Port %d is FREE%s\n", Red, port, NC) + os.Exit(1) + } + + // Port is in use, now find what's using it + process := findProcessUsingPort(port) + if process != nil { + dockerInfo := "" + if process.DockerInfo != "" { + dockerInfo = " " + process.DockerInfo + } + fmt.Printf("%sāœ“ Port %d (%s) in use by %s%s%s %sas PID %s%d%s%s\n", + Green, port, process.Protocol, Bold, process.ProcessName, NC, Green, Bold, process.PID, NC, dockerInfo) + return + } + + // Check if it's a Docker container + containerInfo := findDockerContainerUsingPort(port) + if containerInfo != "" { + fmt.Printf("%sāœ“ Port %d in use by Docker container %s\n", Green, port, containerInfo) + return + } + + // If we still haven't found the process, check for host networking containers more thoroughly + hostNetworkProcess := findHostNetworkingProcess(port) + if hostNetworkProcess != "" { + fmt.Printf("%sāœ“ Port %d likely in use by %s\n", Green, port, hostNetworkProcess) + return + } + + // If we still haven't found the process + fmt.Printf("%s⚠ Port %d is in use but unable to identify the process%s\n", Yellow, port, NC) + + if isDockerAvailable() { + hostContainers := getHostNetworkingContainers() + if len(hostContainers) > 0 { + fmt.Printf("%s Note: Found Docker containers using host networking:%s\n", Cyan, NC) + for _, container := range hostContainers { + cleanImage := cleanImageName(container.Image) + fmt.Printf("%s - %s (%s)%s\n", Cyan, container.Name, cleanImage, NC) + } + fmt.Printf("%s These containers share the host's network, so one of them might be using this port%s\n", Cyan, NC) + } else { + fmt.Printf("%s This might be due to insufficient permissions or the process being in a different namespace%s\n", Cyan, NC) + } + } else { + fmt.Printf("%s This might be due to insufficient permissions or the process being in a different namespace%s\n", Cyan, NC) + } +} + +func isPortInUse(port int) bool { + // Try ss first + if isCommandAvailable("ss") { + cmd := exec.Command("ss", "-tulpn") + output, err := cmd.Output() + if err == nil { + portPattern := fmt.Sprintf(":%d ", port) + return strings.Contains(string(output), portPattern) + } + } + + // Try netstat as fallback + if isCommandAvailable("netstat") { + cmd := exec.Command("netstat", "-tulpn") + output, err := cmd.Output() + if err == nil { + portPattern := fmt.Sprintf(":%d ", port) + return strings.Contains(string(output), portPattern) + } + } + + return false +} + +func findProcessUsingPort(port int) *ProcessInfo { + // Method 1: Try netstat + if process := tryNetstat(port); process != nil { + return process + } + + // Method 2: Try ss + if process := trySS(port); process != nil { + return process + } + + // Method 3: Try lsof + if process := tryLsof(port); process != nil { + return process + } + + // Method 4: Try fuser + if process := tryFuser(port); process != nil { + return process + } + + return nil +} + +func tryNetstat(port int) *ProcessInfo { + if !isCommandAvailable("netstat") { + return nil + } + + cmd := exec.Command("netstat", "-tulpn") + output, err := cmd.Output() + if err != nil { + // Try with sudo if available + if isCommandAvailable("sudo") { + cmd = exec.Command("sudo", "netstat", "-tulpn") + output, err = cmd.Output() + if err != nil { + return nil + } + } else { + return nil + } + } + + scanner := bufio.NewScanner(strings.NewReader(string(output))) + portPattern := fmt.Sprintf(":%d ", port) + + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, portPattern) { + fields := strings.Fields(line) + if len(fields) >= 7 { + pidProcess := fields[6] + parts := strings.Split(pidProcess, "/") + if len(parts) >= 2 { + if pid, err := strconv.Atoi(parts[0]); err == nil { + processName := parts[1] + protocol := fields[0] + dockerInfo := getDockerInfo(pid, processName, port) + return &ProcessInfo{ + PID: pid, + ProcessName: processName, + Protocol: protocol, + DockerInfo: dockerInfo, + } + } + } + } + } + } + + return nil +} + +func trySS(port int) *ProcessInfo { + if !isCommandAvailable("ss") { + return nil + } + + cmd := exec.Command("ss", "-tulpn") + output, err := cmd.Output() + if err != nil { + // Try with sudo if available + if isCommandAvailable("sudo") { + cmd = exec.Command("sudo", "ss", "-tulpn") + output, err = cmd.Output() + if err != nil { + return nil + } + } else { + return nil + } + } + + scanner := bufio.NewScanner(strings.NewReader(string(output))) + portPattern := fmt.Sprintf(":%d ", port) + pidRegex := regexp.MustCompile(`pid=(\d+)`) + + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, portPattern) { + matches := pidRegex.FindStringSubmatch(line) + if len(matches) >= 2 { + if pid, err := strconv.Atoi(matches[1]); err == nil { + processName := getProcessName(pid) + if processName != "" { + fields := strings.Fields(line) + protocol := "" + if len(fields) > 0 { + protocol = fields[0] + } + dockerInfo := getDockerInfo(pid, processName, port) + return &ProcessInfo{ + PID: pid, + ProcessName: processName, + Protocol: protocol, + DockerInfo: dockerInfo, + } + } + } + } + } + } + + return nil +} + +func tryLsof(port int) *ProcessInfo { + if !isCommandAvailable("lsof") { + return nil + } + + cmd := exec.Command("lsof", "-i", fmt.Sprintf(":%d", port), "-n", "-P") + output, err := cmd.Output() + if err != nil { + // Try with sudo if available + if isCommandAvailable("sudo") { + cmd = exec.Command("sudo", "lsof", "-i", fmt.Sprintf(":%d", port), "-n", "-P") + output, err = cmd.Output() + if err != nil { + return nil + } + } else { + return nil + } + } + + scanner := bufio.NewScanner(strings.NewReader(string(output))) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "LISTEN") { + fields := strings.Fields(line) + if len(fields) >= 2 { + processName := fields[0] + if pid, err := strconv.Atoi(fields[1]); err == nil { + dockerInfo := getDockerInfo(pid, processName, port) + return &ProcessInfo{ + PID: pid, + ProcessName: processName, + Protocol: "tcp", + DockerInfo: dockerInfo, + } + } + } + } + } + + return nil +} + +func tryFuser(port int) *ProcessInfo { + if !isCommandAvailable("fuser") { + return nil + } + + cmd := exec.Command("fuser", fmt.Sprintf("%d/tcp", port)) + output, err := cmd.Output() + if err != nil { + return nil + } + + pids := strings.Fields(string(output)) + for _, pidStr := range pids { + if pid, err := strconv.Atoi(strings.TrimSpace(pidStr)); err == nil { + processName := getProcessName(pid) + if processName != "" { + return &ProcessInfo{ + PID: pid, + ProcessName: processName, + Protocol: "tcp", + DockerInfo: "", + } + } + } + } + + return nil +} + +func getProcessName(pid int) string { + cmd := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "comm=") + output, err := cmd.Output() + if err != nil { + return "" + } + return strings.TrimSpace(string(output)) +} + +func getDockerInfo(pid int, processName string, port int) string { + if !isDockerAvailable() { + return "" + } + + // Check if it's docker-proxy (handle truncated names like "docker-pr") + if processName == "docker-proxy" || strings.HasPrefix(processName, "docker-pr") { + containerName := getContainerByPublishedPort(port) + if containerName != "" { + image := getContainerImage(containerName) + cleanImage := cleanImageName(image) + return fmt.Sprintf("%s(Docker: %s, image: %s)%s", Cyan, containerName, cleanImage, NC) + } + return fmt.Sprintf("%s(Docker proxy)%s", Cyan, NC) + } + + // Check if process is in a Docker container using cgroup + containerInfo := getContainerByPID(pid) + if containerInfo != "" { + return fmt.Sprintf("%s(Docker: %s)%s", Cyan, containerInfo, NC) + } + + // Check if this process might be in a host networking container + hostContainer := checkHostNetworkingContainer(pid, processName) + if hostContainer != "" { + return fmt.Sprintf("%s(Docker host network: %s)%s", Cyan, hostContainer, NC) + } + + return "" +} + +func getContainerByPID(pid int) string { + cgroupPath := fmt.Sprintf("/proc/%d/cgroup", pid) + file, err := os.Open(cgroupPath) + if err != nil { + return "" + } + defer file.Close() + + scanner := bufio.NewScanner(file) + containerIDRegex := regexp.MustCompile(`[a-f0-9]{64}`) + + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "docker") { + matches := containerIDRegex.FindStringSubmatch(line) + if len(matches) > 0 { + containerID := matches[0] + containerName := getContainerNameByID(containerID) + if containerName != "" { + return containerName + } + return containerID[:12] + } + } + } + + return "" +} + +func findDockerContainerUsingPort(port int) string { + if !isDockerAvailable() { + return "" + } + + // Check for containers with published ports + cmd := exec.Command("docker", "ps", "--format", "{{.Names}}", "--filter", fmt.Sprintf("publish=%d", port)) + output, err := cmd.Output() + if err != nil { + return "" + } + + containerName := strings.TrimSpace(string(output)) + if containerName != "" { + image := getContainerImage(containerName) + cleanImage := cleanImageName(image) + return fmt.Sprintf("%s%s%s %s(published port, image: %s)%s", Bold, containerName, NC, Cyan, cleanImage, NC) + } + + return "" +} + +func isDockerAvailable() bool { + return isCommandAvailable("docker") +} + +func isCommandAvailable(command string) bool { + _, err := exec.LookPath(command) + return err == nil +} + +func getRunningContainers() []DockerContainer { + if !isDockerAvailable() { + return nil + } + + cmd := exec.Command("docker", "ps", "--format", "{{.Names}}") + output, err := cmd.Output() + if err != nil { + return nil + } + + var containers []DockerContainer + scanner := bufio.NewScanner(strings.NewReader(string(output))) + + for scanner.Scan() { + containerName := strings.TrimSpace(scanner.Text()) + if containerName != "" { + container := DockerContainer{ + Name: containerName, + Image: getContainerImage(containerName), + Ports: getContainerPorts(containerName), + } + containers = append(containers, container) + } + } + + return containers +} + +func getHostNetworkingContainers() []DockerContainer { + if !isDockerAvailable() { + return nil + } + + cmd := exec.Command("docker", "ps", "--format", "{{.Names}}", "--filter", "network=host") + output, err := cmd.Output() + if err != nil { + return nil + } + + var containers []DockerContainer + scanner := bufio.NewScanner(strings.NewReader(string(output))) + + for scanner.Scan() { + containerName := strings.TrimSpace(scanner.Text()) + if containerName != "" { + container := DockerContainer{ + Name: containerName, + Image: getContainerImage(containerName), + Network: "host", + } + containers = append(containers, container) + } + } + + return containers +} + +func getContainerImage(containerName string) string { + cmd := exec.Command("docker", "inspect", containerName) + output, err := cmd.Output() + if err != nil { + return "" + } + + var inspectData []map[string]interface{} + if err := json.Unmarshal(output, &inspectData); err != nil { + return "" + } + + if len(inspectData) > 0 { + if image, ok := inspectData[0]["Config"].(map[string]interface{})["Image"].(string); ok { + return image + } + } + + return "" +} + +func getContainerPorts(containerName string) []PortMapping { + cmd := exec.Command("docker", "port", containerName) + output, err := cmd.Output() + if err != nil { + return nil + } + + var ports []PortMapping + scanner := bufio.NewScanner(strings.NewReader(string(output))) + portRegex := regexp.MustCompile(`(\d+)/(tcp|udp) -> 0\.0\.0\.0:(\d+)`) + ipv6PortRegex := regexp.MustCompile(`(\d+)/(tcp|udp) -> \[::\]:(\d+)`) + + for scanner.Scan() { + line := scanner.Text() + + // Check for IPv4 + if matches := portRegex.FindStringSubmatch(line); len(matches) >= 4 { + containerPort, _ := strconv.Atoi(matches[1]) + protocol := matches[2] + hostPort, _ := strconv.Atoi(matches[3]) + + ports = append(ports, PortMapping{ + ContainerPort: containerPort, + HostPort: hostPort, + Protocol: protocol, + IPv6: false, + }) + } + + // Check for IPv6 + if matches := ipv6PortRegex.FindStringSubmatch(line); len(matches) >= 4 { + containerPort, _ := strconv.Atoi(matches[1]) + protocol := matches[2] + hostPort, _ := strconv.Atoi(matches[3]) + + ports = append(ports, PortMapping{ + ContainerPort: containerPort, + HostPort: hostPort, + Protocol: protocol, + IPv6: true, + }) + } + } + + return ports +} + +func getContainerByPublishedPort(port int) string { + cmd := exec.Command("docker", "ps", "--format", "{{.Names}}", "--filter", fmt.Sprintf("publish=%d", port)) + output, err := cmd.Output() + if err != nil { + return "" + } + return strings.TrimSpace(string(output)) +} + +func getContainerNameByID(containerID string) string { + cmd := exec.Command("docker", "inspect", containerID) + output, err := cmd.Output() + if err != nil { + return "" + } + + var inspectData []map[string]interface{} + if err := json.Unmarshal(output, &inspectData); err != nil { + return "" + } + + if len(inspectData) > 0 { + if name, ok := inspectData[0]["Name"].(string); ok { + return strings.TrimPrefix(name, "/") + } + } + + return "" +} + +func cleanImageName(image string) string { + // Remove SHA256 hashes + shaRegex := regexp.MustCompile(`sha256:[a-f0-9]*`) + cleaned := shaRegex.ReplaceAllString(image, "[image-hash]") + + // Remove registry prefixes, keep only the last part + parts := strings.Split(cleaned, "/") + if len(parts) > 0 { + return parts[len(parts)-1] + } + + return cleaned +} + +func findHostNetworkingProcess(port int) string { + if !isDockerAvailable() { + return "" + } + + // Get all host networking containers + hostContainers := getHostNetworkingContainers() + + for _, container := range hostContainers { + // Check if this container might be using the port + if isContainerUsingPort(container.Name, port) { + cleanImage := cleanImageName(container.Image) + return fmt.Sprintf("%s%s%s %s(Docker host network: %s)%s", Bold, container.Name, NC, Cyan, cleanImage, NC) + } + } + + return "" +} + +func isContainerUsingPort(containerName string, port int) bool { + // Try to execute netstat inside the container to see if it's listening on the port + cmd := exec.Command("docker", "exec", containerName, "sh", "-c", + fmt.Sprintf("netstat -tlnp 2>/dev/null | grep ':%d ' || ss -tlnp 2>/dev/null | grep ':%d '", port, port)) + output, err := cmd.Output() + if err != nil { + return false + } + + return len(output) > 0 +} + +func checkHostNetworkingContainer(pid int, processName string) string { + if !isDockerAvailable() { + return "" + } + + // Get all host networking containers and check if any match this process + hostContainers := getHostNetworkingContainers() + + for _, container := range hostContainers { + // Try to find this process inside the container + cmd := exec.Command("docker", "exec", container.Name, "sh", "-c", + fmt.Sprintf("ps -o pid,comm | grep '%s' | grep -q '%d\\|%s'", processName, pid, processName)) + err := cmd.Run() + if err == nil { + cleanImage := cleanImageName(container.Image) + return fmt.Sprintf("%s (%s)", container.Name, cleanImage) + } + } + + return "" +} diff --git a/zed/settings.json b/zed/settings.json index 06860d6..874041e 100644 --- a/zed/settings.json +++ b/zed/settings.json @@ -15,9 +15,7 @@ "host": "desktop", "projects": [ { - "paths": [ - "/home/menno/.dotfiles" - ] + "paths": ["/home/menno/.dotfiles"] } ] }