diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c43fcfd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +.github +*.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..366e7c5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,121 @@ +name: Continuous Integration Pipeline + +on: + release: + types: + - published + # - released + # - created + + workflow_dispatch: + push: + branches: [ main ] + # tags: + # - 'v[0-9]+.[0-9]+.[0-9]+' + + pull_request: + branches: [ main ] + +env: + GO_VERSION: '1.24' + MAIN_FILE: './cmd/server/hostinfo.go' + +jobs: + + # Verification: + # if: github.ref_name != 'main' + # name: Verification of all checks + # runs-on: ubuntu-latest + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Set up Go ${{ env.GO_VERSION }} + # uses: actions/setup-go@v5 + # with: + # go-version: ${{ env.GO_VERSION }} + + # # - name: Cache Go modules + # # uses: actions/cache@v3 + # # with: + # # path: | + # # ~/go/pkg/mod + # # ~/.cache/go-build + # # key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + # # restore-keys: | + # # ${{ runner.os }}-go- + + + # - name: Run Lint + # run: | + # echo "Running lint..." + # fmt_out=$(gofmt -l .) + # if [ -n "$fmt_out" ]; then + # echo "Go code is not formatted:" + # echo "$fmt_out" + # gofmt -d . + # exit 1 + # fi + # go vet ./... + + # - name: Run tests + # run: | + # echo "Running tests..." + # go mod download + # go test -v -race ./... + # go build -v ${{ env.MAIN_FILE }} + + # - name: Run Security + # run: | + # echo "Running security checks..." + + # # Hardcoded credentials check + # # Exclude common directories and files + # EXCLUDES=(".git" "frontend" "node_modules" "build" "docs") + # # Exclude file patterns + # FILE_EXCLUDES=("*.yml" "*.yaml" "*.md" "*.js" "*.jsx" "*.json" "*_test.go") + # # Build grep exclude params + # EXCLUDE_PARAMS=() + # for dir in "${EXCLUDES[@]}"; do EXCLUDE_PARAMS+=(--exclude-dir="$dir"); done + # for file in "${FILE_EXCLUDES[@]}"; do EXCLUDE_PARAMS+=(--exclude="$file"); done + # # Run grep safely + # if grep -rI "password.*=" . "${EXCLUDE_PARAMS[@]}" | \ + # grep -v "^[[:space:]]*//" | \ + # grep -v 'json:' | \ + # grep -v '`json:' | \ + # grep -v "Password.*string" | \ + # grep -v "r.BasicAuth()" | \ + # grep -v "subtle.ConstantTimeCompare"; then + # echo "Potential hardcoded credentials found" + # exit 1 + # fi + + # build-and-publish-docker: + # if: github.ref_name != 'main' + # needs: [Verification] + # uses: ./.github/workflows/template-docker.yml + # with: + # dockerfile_path: "./docker/Dockerfile" + # docker_push: true + # image_name: "hostinfo" + # version: ${{ github.ref_name }} + # registry: "docker.io" + # organization: "maximleus" + # platforms: "linux/amd64,linux/arm64" + # secrets: + # DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + # DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + + semantic-release: + if: > + github.event_name == 'push' && github.ref == 'refs/heads/main' + permissions: + contents: write # to be able to publish a GitHub release + uses: ./.github/workflows/template-semantic-release.yml + with: + dry_run: false + semantic_version: 20 + secrets: + MY_GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + \ No newline at end of file diff --git a/.github/workflows/template-docker.yml b/.github/workflows/template-docker.yml new file mode 100644 index 0000000..8009b67 --- /dev/null +++ b/.github/workflows/template-docker.yml @@ -0,0 +1,221 @@ +name: Docker Template Workflow for GitHub Actions + +on: + workflow_call: + inputs: + dockerfile_path: + description: "Path to the Dockerfile" + required: false + default: "./Dockerfile" + type: string + docker_image_overwrite: + description: "Overwrite existing Docker image if it exists" + required: false + type: boolean + default: false + docker_push: + description: "If true, the Docker image will be built and pushed" + required: false + type: boolean + default: false + image_name: + description: "Docker image name (without registry)" + required: true + type: string + version: + description: "Tag version (ex: 1.0.0, latest, dev)" + required: true + type: string + registry: + description: "Registry (docker.io, ghcr.io, etc)" + required: false + default: "docker.io" + type: string + organization: + description: "Docker Hub or GHCR organization/user" + required: true + type: string + platforms: + description: "Platforms to build (comma separated)" + required: false + default: "linux/amd64,linux/arm64" + type: string + secrets: + DOCKER_USERNAME: + required: true + DOCKER_PASSWORD: + required: true + +permissions: + contents: read + # packages: write + # security-events: write + +jobs: + docker-build-push: + if: "!contains(github.event.head_commit.message, '[docker skip]')" + name: Build & Publish Docker Image + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + if: ${{ inputs.registry == 'docker.io' }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log in to GHCR + if: ${{ inputs.registry == 'ghcr.io' }} + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set dynamic version + id: set_version + run: | + # detect PR source branch or fallback to ref_name + VERSION="${{ github.event_name == 'pull_request' && github.head_ref || inputs.version }}" + + # Sanitize for Docker tags: replace / , with - + VERSION="${VERSION//\//-}" + VERSION="${VERSION//,/ -}" + + echo "Dynamic version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Verification if Dockerfile exists + if: ${{ inputs.dockerfile_path != '' }} + run: | + if [ ! -f "${{ inputs.dockerfile_path }}" ]; then + echo "Dockerfile not found at path: ${{ inputs.dockerfile_path }}" + exit 1 + fi + + - name: Verification if Docker image already exists + id: check_image + run: | + IMAGE="${{ inputs.organization }}/${{ inputs.image_name }}:${{ steps.set_version.outputs.version }}" + OVERWRITE="${{ inputs.docker_image_overwrite }}" + EXISTS=false + + echo "Checking if image exists: $IMAGE" + + if docker manifest inspect "$IMAGE" > /dev/null 2>&1; then + echo "Image exists: $IMAGE" + if [ "$OVERWRITE" = "true" ]; then + echo "Overwrite enabled → will rebuild" + EXISTS=false + else + EXISTS=true + fi + else + echo "Image does not exist → will build" + fi + + echo "exists=$EXISTS" >> $GITHUB_OUTPUT + echo "overwrite=$OVERWRITE" >> $GITHUB_OUTPUT + + - name: Set docker_push for feature branches + id: check_feature_branch + run: | + # Determine ref name + REF_NAME="${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}" + + # Decide docker_push + if [[ "$REF_NAME" == feature/* ]]; then + echo "Feature branch detected, setting docker_push to false" + echo "docker_push=false" >> $GITHUB_OUTPUT + else + echo "Not a feature branch, using input docker_push value" + echo "docker_push=${{ inputs.docker_push }}" >> $GITHUB_OUTPUT + fi + + - name: Set Docker tags + id: docker_tags + run: | + VERSION_TAG="${{ inputs.organization }}/${{ inputs.image_name }}:${{ steps.set_version.outputs.version }}" + echo "tags=$VERSION_TAG" >> "$GITHUB_OUTPUT" + + if [ "${{ github.event_name }}" = "release" ] && [ "${{ github.event.action }}" = "published" ]; then + LATEST_TAG="${{ inputs.organization }}/${{ inputs.image_name }}:latest" + echo "tags=${VERSION_TAG},${LATEST_TAG}" >> "$GITHUB_OUTPUT" + fi + + # - name: Extract metadata for Docker + # id: meta + # uses: docker/metadata-action@v5 + # with: + # images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # tags: | + # # Branch name + # type=ref,event=branch + # # Tag name + # type=ref,event=tag + # # Short SHA + # type=sha,prefix=sha- + # # Latest tag for main branch + # type=raw,value=latest,enable={{is_default_branch}} + # # Semver tags + # type=semver,pattern={{version}} + # type=semver,pattern={{major}}.{{minor}} + # type=semver,pattern={{major}} + + - name: Build and push Docker image + if: steps.check_image.outputs.exists == 'false' || steps.check_image.outputs.overwrite == 'true' + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ inputs.dockerfile_path }} + push: ${{ steps.check_feature_branch.outputs.docker_push }} + platforms: ${{ inputs.platforms }} + tags: ${{ steps.docker_tags.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Summary + run: | + if [ "${{ inputs.docker_push }}" = "false" ]; then + echo "Dry run mode: image was built but not pushed." + else + echo "Image published to:" + echo "${{ inputs.registry }}/${{ inputs.organization }}/${{ inputs.image_name }}:${{ steps.set_version.outputs.version }}" + fi + + # security-scan: + # name: Security Scan + # runs-on: ubuntu-latest + # needs: build + # if: github.event_name != 'pull_request' + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Log in to Docker Hub + # uses: docker/login-action@v3 + # with: + # username: ${{ secrets.DOCKERHUB_USERNAME }} + # password: ${{ secrets.DOCKERHUB_TOKEN }} + + # - name: Run Trivy vulnerability scanner + # uses: aquasecurity/trivy-action@master + # with: + # image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}' + # format: 'sarif' + # output: 'trivy-results.sarif' + # severity: 'CRITICAL,HIGH' + + # - name: Upload Trivy scan results + # uses: github/codeql-action/upload-sarif@v4 + # if: always() + # with: + # sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/template-semantic-release.yml b/.github/workflows/template-semantic-release.yml new file mode 100644 index 0000000..2d15264 --- /dev/null +++ b/.github/workflows/template-semantic-release.yml @@ -0,0 +1,89 @@ +name: Semantic Release +on: + workflow_call: + inputs: + dry_run: + description: "Run the release in dry run mode" + required: false + type: boolean + default: false + semantic_version: + description: "Semantic versioning mode (e.g., 20 for v2.0.0)" + required: false + type: number + default: 20 + outputs: + new_release_published: + description: "Indicates if a new release was published" + value: ${{ jobs.release.outputs.new_release_published }} + new_release_version: + description: "The version of the new release" + value: ${{ jobs.release.outputs.new_release_version }} + new_release_major_version: + description: "The major version of the new release" + value: ${{ jobs.release.outputs.new_release_major_version }} + new_release_minor_version: + description: "The minor version of the new release" + value: ${{ jobs.release.outputs.new_release_minor_version }} + new_release_patch_version: + description: "The patch version of the new release" + value: ${{ jobs.release.outputs.new_release_patch_version }} + secrets: + MY_GITHUB_TOKEN: + required: true + + +jobs: + release: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + name: Release + runs-on: ubuntu-latest + permissions: # Required permissions for semantic-release action + contents: write # to be able to publish a GitHub release + # issues: write # to be able to comment on released issues + # pull-requests: write # to be able to comment on released pull requests + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + # - name: Install dependencies + # run: | + # if [ ! -f package.json ]; then + # cat < .releaserc.yaml + # plugins: + # - "@semantic-release/commit-analyzer" + # - "@semantic-release/release-notes-generator" + # - "@semantic-release/changelog" + # - "@semantic-release/git" + # - "@semantic-release/github" + + # branches: + # - "+([0-9])?(.{+([0-9]),x}).x" + # - main + # - fs/pl/start + # EOF + # fi + + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v5 + id: semantic # Need an `id` for output variables + with: + dry_run: ${{ inputs.dry_run }} + semantic_version: ${{ inputs.semantic_version }} + env: + GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} + + - name: Do something when a new release published + if: steps.semantic.outputs.new_release_published == 'true' + run: | + echo ${{ steps.semantic.outputs.new_release_version }} + echo ${{ steps.semantic.outputs.new_release_major_version }} + echo ${{ steps.semantic.outputs.new_release_minor_version }} + echo ${{ steps.semantic.outputs.new_release_patch_version }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6d98ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Code coverage profiles and other test artifacts +*.out +coverage.* +*.coverprofile +profile.cov + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +# Editor/IDE +# .idea/ +# .vscode/ +# .releaserc* \ No newline at end of file diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..2fd4f2c --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,17 @@ +{ + "branches": ["main", "+([0-9])?(.{+([0-9]),x}).x"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + + [ "@semantic-release/changelog", {"changelogFile": "CHANGELOG.md"} ], + [ + "@semantic-release/git", + { + "assets": ["CHANGELOG.md"], + "message": "chore(release): ${nextRelease.version} [toster]\n\n${nextRelease.notes}" + } + ], + "@semantic-release/github" + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9820e2c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# 1.0.0 (2026-01-16) + + +### Features + +* Initial commit ([b223b7c](https://github.com/MaksymLeus/hostinfo/commit/b223b7c6f721ce887b1213c3f7e80753da930f9b)) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5f88e9d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Maksym Leus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README copy.md b/README copy.md new file mode 100644 index 0000000..f6cd778 --- /dev/null +++ b/README copy.md @@ -0,0 +1,240 @@ +# Go Host Info Web App +[![Go Version](https://img.shields.io/badge/Go-1.20+-00ADD8?style=flat&logo=go)](https://golang.org) +[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) + +A lightweight **Golang web application** that displays detailed runtime information about the **host, container and cloud environment**. + +Designed to run **as-is**: +- locally +- in Docker +- on AWS / GCP / Azure + +✅ No special permissions +✅ No cloud credentials +✅ Safe for production + +## ✨ Features + +### 🖥 Host & Container Info +- Hostname +- OS / Architecture +- Go version +- Container uptime +- Environment variables + +### ☁️ Cloud Auto-Detection (no creds) +- AWS EC2 (instance ID, region, AZ, type) +- Google Cloud (project, zone, machine type) +- Azure (VM detection) +- Local / Docker fallback + +### 🌐 Network +- IP addresses +- MAC addresses + +### 🎨 UI +- Clean dark UI +- Structured sections +- Human-readable layout +- Browser-friendly dashboard + +## 📸 Screenshot (example) +![alt text](./web/image.png) + +## 📦 Prerequisites +- **Go**: Version 1.21 or higher ([Download](https://golang.org/dl/)) + +## 🚀 Installation + +### Clone the Repository + +```bash +git clone https://github.com/MaksymLeus/hostinfo.git +cd hostinfo +``` +### Install Dependencies + +#### Backend +```bash +go mod download +``` +## ⚡ Quick Start + +### Build and Run + +```bash +# Build the application +./build.sh + +# Run the server +./hostinfo +``` +Access the application at `http://localhost:8080` + +## 🌐 Deployment + +### Linux Server + +```bash +# Build for Linux +./build.sh all + +# Copy to server +scp bin/hostinfo-linux-x64 user@server:/opt/hostinfo/hostinfo + +# Run on server +ssh user@server +cd /opt/hostinfo +./hostinfo +``` +### systemd Service + +Create `/etc/systemd/system/hostinfo.service`: + +```ini +[Unit] +Description=Web Hostinfo Service +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/opt/hostinfo +ExecStart=/opt/hostinfo/hostinfo +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` + +Enable and start: + +```bash +sudo systemctl enable hostinfo +sudo systemctl start hostinfo +sudo systemctl status hostinfo +``` + +## Preparation with Docker 🐳 +Hostinfo is available as a Docker image for easy deployment. + +**Image Details:** +- **Registry:** Docker Hub ([`maximleus/hostinfo`](https://hub.docker.com/r/maximleus/hostinfo)) +- **Base Image:** `golang:1.24-alpine` +- **Platforms:** `linux/amd64`, `linux/arm64` +- **Size:** ~100MB compressed + + +**Quick Start with Docker Compose:** +```bash +# Build the image +docker compose build +# Start with default settings +docker compose up -d +# Stop +docker compose down +# View logs +docker compose logs -f +``` +**Or Quick Start directly:** +```bash +# Build the image +docker build -t hostinfo . +# Run container +docker run -p 8080:8080 hostinfo +``` +Access the application at `http://localhost:8080` + +## 💻 Development + +### Backend Development + +```bash +# Run with hot reload (use air or similar) +go run cmd/server/main.go + +# Run tests +go test ./... + +# Run tests with coverage +go test -cover ./... + +# Format code +go fmt ./... + +# Lint code +go vet ./... +``` +### Development Workflow + +1. Start backend: + ```bash + go run cmd/server/main.go + ``` +2. Open `http://localhost:3000` for hot-reload development + +### Project Structure + + +```text +hostinfo +├── .dockerignore # Files/folders to ignore when building Docker images +├── .github +│ └── workflows +│ ├── ci.yml # Main CI workflow: tests, lint, security +│ ├── template-docker.yml # Reusable Docker build & push workflow +│ └── template-semantic-release.yml # Reusable Semantic Release workflow +├── .gitignore # Git ignore rules +├── LICENSE.md # MIT license for the project +├── README.md # Project overview, usage, and instructions +├── TODO.md # TODO list for future development +├── build.sh # Optional build script for local or CI builds +├── cmd +│ └── server +│ └── hostinfo.go # Main Go server entrypoint +├── docker +│ ├── Dockerfile # Dockerfile for building container +│ └── docker-compose.yml # Docker Compose for multi-service setups +├── docs +│ └── semantic.md # Documentation for semantic release workflow +├── go.mod # Go module definition +├── internal +└── web + ├── image.png # Example image used in web UI + └── templates + ├── index.html # Main HTML template for the host info page + └── index_to_update.html # Optional template used for dynamic updates +``` +#### Notes on structure: +- `.github/workflows`: All CI/CD workflows are here. Reusable templates (`template-docker.yml` and `template-semantic-release.yml`) make it easy to trigger builds or releases from other workflows. + +- `cmd/server/hostinfo.go`: Go main entrypoint; you could add more commands in `cmd/` if needed. + +- `web/templates`: HTML templates for rendering your host info page. + +- `docker`: Contains Docker-related files. `docker-compose.yml` is optional but useful for multi-container setups. + +- `docs/semantic.md`: Full guide on how semantic release works in this project. + + +## 🤝 Contributing + +Contributions are welcome! Please follow these steps: + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'feat: Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +### Development Guidelines + +- Follow Go best practices and conventions +- Write tests for new features +- Update documentation as needed +- Ensure all tests pass before submitting PR +- Keep commits atomic and well-described + +## 📝 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details. \ No newline at end of file diff --git a/cmd/server/hostinfo.go b/cmd/server/hostinfo.go new file mode 100644 index 0000000..3751858 --- /dev/null +++ b/cmd/server/hostinfo.go @@ -0,0 +1,263 @@ +package main + +import ( + "fmt" + "html/template" + "io" + "log" + "net" + "net/http" + "os" + "runtime" + "time" +) + +type HostInfo struct { + Hostname string + IPs []string + MACs []string + OS string + Arch string + GoVersion string + StartTime string + Now string + Env map[string]string + Cloud CloudInfo + Kubernetes KubernetesInfo +} + +type CloudInfo struct { + Provider string + Region string + Zone string + Instance string + Extra map[string]string +} + +type KubernetesInfo struct { + Enabled bool + PodName string + PodNamespace string + PodIP string + NodeName string + ServiceAccount string + Container string +} + +var startTime = time.Now() + +func main() { + tmpl := template.Must(template.ParseFiles("web/templates/index.html")) + + http.HandleFunc("/healthz", healthHandler) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + info := HostInfo{ + Hostname: getHostname(), + IPs: getIPs(), + MACs: getMACs(), + OS: runtime.GOOS, + Arch: runtime.GOARCH, + GoVersion: runtime.Version(), + StartTime: startTime.Format(time.RFC3339), + Now: time.Now().Format(time.RFC3339), + Env: getEnv(), + Cloud: detectCloud(), + } + + _ = tmpl.Execute(w, info) + }) + + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + fmt.Printf("Server listening on :%s --> http://localhost:%s", port, port) + if err := http.ListenAndServe(":"+port, nil); err != nil { + log.Fatal(err) + } + +} +func healthHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"status":"ok"}`)) + +} + +func getHostname() string { + h, _ := os.Hostname() + return h +} + +func getIPs() []string { + var ips []string + ifaces, _ := net.Interfaces() + for _, i := range ifaces { + addrs, _ := i.Addrs() + for _, a := range addrs { + if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + ips = append(ips, ipnet.IP.String()) + } + } + } + return ips +} + +func getMACs() []string { + var macs []string + ifaces, _ := net.Interfaces() + for _, i := range ifaces { + if i.HardwareAddr != nil { + macs = append(macs, i.HardwareAddr.String()) + } + } + return macs +} + +func getEnv() map[string]string { + env := make(map[string]string) + for _, e := range os.Environ() { + kv := []rune(e) + for i, c := range kv { + if c == '=' { + env[string(kv[:i])] = string(kv[i+1:]) + break + } + } + } + return env +} + +func detectCloud() CloudInfo { + if aws := detectAWS(); aws.Provider != "" { + return aws + } + if gcp := detectGCP(); gcp.Provider != "" { + return gcp + } + if azure := detectAzure(); azure.Provider != "" { + return azure + } + return CloudInfo{Provider: "local"} +} + +func detectAWS() CloudInfo { + client := http.Client{Timeout: 500 * time.Millisecond} + + req, _ := http.NewRequest("GET", + "http://169.254.169.254/latest/meta-data/instance-id", nil) + + resp, err := client.Do(req) + if err != nil || resp.StatusCode != 200 { + return CloudInfo{} + } + defer resp.Body.Close() + + id, _ := io.ReadAll(resp.Body) + + region := awsMeta("placement/region") + zone := awsMeta("placement/availability-zone") + + return CloudInfo{ + Provider: "aws", + Region: region, + Zone: zone, + Instance: string(id), + Extra: map[string]string{ + "AMI": awsMeta("ami-id"), + "Type": awsMeta("instance-type"), + }, + } +} + +func awsMeta(path string) string { + client := http.Client{Timeout: 300 * time.Millisecond} + resp, err := client.Get("http://169.254.169.254/latest/meta-data/" + path) + if err != nil { + return "" + } + defer resp.Body.Close() + b, _ := io.ReadAll(resp.Body) + return string(b) +} + +func detectGCP() CloudInfo { + client := http.Client{Timeout: 500 * time.Millisecond} + + req, _ := http.NewRequest("GET", + "http://metadata.google.internal/computeMetadata/v1/instance/id", nil) + req.Header.Set("Metadata-Flavor", "Google") + + resp, err := client.Do(req) + if err != nil || resp.StatusCode != 200 { + return CloudInfo{} + } + defer resp.Body.Close() + + id, _ := io.ReadAll(resp.Body) + + return CloudInfo{ + Provider: "gcp", + Region: gcpMeta("instance/region"), + Zone: gcpMeta("instance/zone"), + Instance: string(id), + Extra: map[string]string{ + "Machine": gcpMeta("instance/machine-type"), + "Project": gcpMeta("project/project-id"), + }, + } +} + +func gcpMeta(path string) string { + client := http.Client{Timeout: 300 * time.Millisecond} + req, _ := http.NewRequest("GET", + "http://metadata.google.internal/computeMetadata/v1/"+path, nil) + req.Header.Set("Metadata-Flavor", "Google") + + resp, err := client.Do(req) + if err != nil { + return "" + } + defer resp.Body.Close() + b, _ := io.ReadAll(resp.Body) + return string(b) +} + +func detectAzure() CloudInfo { + client := http.Client{Timeout: 500 * time.Millisecond} + + req, _ := http.NewRequest("GET", + "http://169.254.169.254/metadata/instance?api-version=2021-02-01", nil) + req.Header.Set("Metadata", "true") + + resp, err := client.Do(req) + if err != nil || resp.StatusCode != 200 { + return CloudInfo{} + } + defer resp.Body.Close() + + return CloudInfo{ + Provider: "azure", + Extra: map[string]string{ + "VM": "Azure VM detected", + }, + } +} + +func detectKubernetes() KubernetesInfo { + // Always present in k8s + if os.Getenv("KUBERNETES_SERVICE_HOST") == "" { + return KubernetesInfo{Enabled: false} + } + + return KubernetesInfo{ + Enabled: false, //true, set to false to avoid showing k8s info in the UI until all is verified + PodName: "POD_NAME", + PodNamespace: "POD_NAMESPACE", + PodIP: "POD_IP", + NodeName: "NODE_NAME", + ServiceAccount: "SERVICE_ACCOUNT", + Container: "CONTAINER_NAME", + } +} diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..7625074 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,98 @@ +# Stage 1: Build the Go binary +FROM golang:1.24-bookworm AS go-builder + +# Install build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + git ca-certificates tzdata \ + && rm -rf /var/lib/apt/lists/* + + +WORKDIR /app + +# Copy go mod files first for better caching +COPY go.mod ./ +# COPY go.mod go.sum ./ +RUN go mod download + +# Copy source code +COPY cmd/ ./cmd/ +# COPY internal/ ./internal/ +COPY web/ ./web/ + +# Build arguments for multi-platform support +ARG TARGETOS=linux +ARG TARGETARCH + +# Build the binary for target platform +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \ + -ldflags="-s -w -X main.Version=docker" \ + -o /app/hostinfo.go \ + ./cmd/server/hostinfo.go + +# Final stage: Debian-based runtime for proper bash support +FROM debian:bookworm-slim + +# Install required packages: +# - ca-certificates: for HTTPS connections +# - tzdata: for timezone support +# - bash: full bash shell for script execution +# - coreutils: standard Unix utilities +# - curl: for health checks and HTTP operations +# - openssh-client: for SSH connections to remote servers +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + tzdata \ + bash \ + zsh \ + coreutils \ + curl \ + openssh-client \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Create non-root user for security +# Add user to tty group for PTY access (required for interactive terminal) +RUN groupadd -g 1000 hostinfo && \ + useradd -u 1000 -g hostinfo -G tty -s /bin/bash -m hostinfo + +# Create data and config directories +RUN mkdir -p /data /config && \ + chown -R hostinfo:hostinfo /data /config + +# Create .ssh directory for SSH key operations +RUN mkdir -p /home/hostinfo/.ssh && \ + chown hostinfo:hostinfo /home/hostinfo/.ssh && \ + chmod 700 /home/hostinfo/.ssh + +# Create tmp directory for session files (SSH configs, keys, wrappers) +# This ensures the non-root user can create temp files for terminal sessions +RUN mkdir -p /home/hostinfo/tmp && \ + chown hostinfo:hostinfo /home/hostinfo/tmp && \ + chmod 700 /home/hostinfo/tmp + +WORKDIR /app + +# Copy binary from builder +COPY --from=go-builder /app/hostinfo.go /app/hostinfo.go + +# Set ownership +RUN chown hostinfo:hostinfo /app/hostinfo.go + +# Switch to non-root user +USER hostinfo + +# Expose default port +EXPOSE 8080 + +# Health check using curl +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ + CMD curl -sf http://localhost:8080/healthz || exit 1 + +# Environment variables with defaults +# TMPDIR is set to user's home for proper temp file creation in non-root context +# hadolint ignore=DL3044 +ENV TMPDIR=/home/hostinfo/tmp \ + SHELL=/bin/bash + +# Run the application +ENTRYPOINT ["/app/hostinfo.go"] \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..89b4998 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" + +services: + hostinfo: + image: hostinfo:latest + container_name: hostinfo + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"] + interval: 30s + timeout: 3s + retries: 3 + environment: + ENV: local + SERVICE: hostinfo + PORT: 8080 + restart: unless-stopped diff --git a/docs/00-overview.md b/docs/00-overview.md new file mode 100644 index 0000000..032661d --- /dev/null +++ b/docs/00-overview.md @@ -0,0 +1,167 @@ + +# HostInfo — Project Overview + +HostInfo is a lightweight Go-based service that exposes system information through an HTML dashboard and a JSON API. +It is designed for DevOps engineers, SREs, system administrators, and automation pipelines that need quick and portable access to machine telemetry. + +## 🎯 Project Goals + +- Provide a self-hosted tool to inspect system information +- Offer both **human-readable web UI** and **machine-readable API** +- Be lightweight, dependency-free, and easy to deploy +- Support Docker, Docker Compose, and standard Linux service management +- Integrate cleanly into CI/CD and automation environments + +## 🧩 Key Features + +- 🌐 **Web Dashboard** — view host info in a browser +- 📡 **REST API** — extract data programmatically +- 🐳 **Container-Ready** — minimal Docker image support +- 📦 **Binary or Docker Deployment** +- ⚙️ **Configurable** via env or CLI flags +- 🔐 **Zero External Dependencies** +- 📂 **Clean Codebase** with docs + CI + +## 🖥 What Information Does It Show? + +HostInfo exposes hardware and OS metrics such as: + +- Hostname +- OS & kernel details +- CPU model & core count +- Memory information +- Envierment veriabels +- Disk space (planned) +- Network details (planned) + +These can be improved or extended over time (CPU usage %, disk IO, network stats, etc). + +## 🧱 Architecture Overview + +HostInfo is structured as a simple web server with the following logical layers: + +| Layers | Description | +|:---|:---| +| Web UI | ➜ HTML Templates | +| HTTP API | ➜ JSON Responses | +| System Information | ➜ OS / CPU / Memory | +| Runtime | ➜ Go 1.22+ | + + +## 🗂 Repository Structure (High-Level) +```bash +hostinfo/ +├── cmd/server # Application entrypoint +├── internal/ # Core internal logic +├── web/ # HTML templates, static assets +├── docker/ # Docker + Compose files +├── docs/ # Documentation +├── .github/workflows/ # CI/CD pipelines +├── scripts/hooks/ # Git hooks (pre-commit, commit-msg) +└── tools/ # Helper scripts (bootstrap, dev) +``` + +More details in: `07-development.md` + +## 🛠 Tech Stack + +| Category | Choice | +|-------------|---------------| +| Language | Go (1.22+) | +| Runtime | Standard Lib | +| UI | HTML Templates| +| Packaging | Docker & Go | +| CI/CD | GitHub Actions| +| Release | semantic-release| + +No external dependencies are required for core features. + +## 📦 Deployment Models + +### HostInfo supports multiple deployment targets: + +- **Local Binary** + - For Linux/macOS/Windows workstations. + +- **Docker Container** + - For servers, homelabs, CI automation. + +- **Docker Compose** + - Part of larger observability stacks. + +- **Systemd Service (Optional)** + - For persistent Linux deployments. + +Kubernetes deployment manifests may be added later. + +## 🧑‍💻 Target Users + +HostInfo is intended for: + +- DevOps / SREs +- Platform / Infra engineers +- System administrators +- Automation pipelines +- Observability stack maintainers +- Homelab enthusiasts + +## 🪄 Use Cases + +Common usage patterns include: + +- Checking remote machine details via web browser +- Collecting telemetry in CI/CD jobs +- Integrating system info into dashboards +- Baseline validation for provisioning +- Self-hosted server inventory in homelabs +- Lightweight monitoring for edge devices + +## 🏁 Project Status + +**Current Stage:** Early Development +Core features are functional, and additional modules (disk, network, metrics, authentication) are planned. + +Upcoming enhancements include: + +- Disk usage collection +- Network stats +- Auth (optional Basic/OAuth) +- Metrics (Prometheus endpoint) +- Swagger/OpenAPI API reference +- Improved UI front-end + +--- + +## 📜 License + +This project is licensed under the **MIT License**. +See `LICENSE.md` for full details. + +--- + +## 🤝 Contributing + +Contributions are welcome! +Please see: + +- `docs/07-development.md` +- `docs/09-releasing.md` (if semantic-release involved) + +--- + +## ⭐ Summary + +HostInfo aims to streamline the collection of host-level data in a way that is: + +✔ Fast +✔ Portable +✔ Single-binary +✔ Self-hosted +✔ API-friendly +✔ DevOps-ready + +It bridges the gap between “simple Linux commands” and “full monitoring stacks” by providing a clean, lightweight, and flexible utility for machine introspection. + + + + diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md new file mode 100644 index 0000000..094958b --- /dev/null +++ b/docs/01-getting-started.md @@ -0,0 +1,145 @@ + +# Getting Started + +This guide will help you get HostInfo running on your machine using the quickest and simplest methods. +Whether you prefer to build from source or use Docker, this document has you covered. + +--- + +## ✅ Prerequisites + +HostInfo supports multiple environments, but depending on your preferred setup, you may need: + +### **For local builds** +- Go **1.22+** +- Git + +### **For containerized deployments** +- Docker **20+** +- (Optional) Docker Compose **v2+** + +### **Supported Operating Systems** +| OS | Status | +|----------|------------| +| Linux | ✔ Supported | +| macOS | ✔ Supported | +| Windows | ✔ Supported | + +No external Go dependencies are required for the default build. + +--- + +## 📦 Step 1 — Clone Repository +```bash +git clone https://github.com/yourname/hostinfo.git +cd hostinfo +``` +If you plan to contribute, fork first and clone your fork instead. + +## 🧱 Step 2 — Choose an Installation Method + +### HostInfo can be run in three ways: + +#### Option A — 🏁 Local Go Build (Recommended for Dev) +This method compiles and runs the binary directly. + +**Build** +```bash +go build -o hostinfo ./cmd/server +``` +**Run** +```bash +./hostinfo +``` +Open your browser and visit: `http://localhost:8080` + +#### Option B — 🐳 Docker Container (No Go Needed) +**Build image** +```bash +docker build -t hostinfo:latest ./docker +``` +**Run container** +```bash +docker run -it --rm -p 8080:8080 hostinfo:latest +``` +Open your browser and visit: `http://localhost:8080` + +#### Option C — 🐙 Docker Compose (Services Setup) + +This method is useful for multi-service environments. + +**Start** +```bash +docker compose up -d +``` +**Stop** +```bash +docker compose down +``` +Open your browser and visit: `http://localhost:8080` + + +## ⚙️ Configuration Basics +HostInfo can be configured via: +- Environment variables +- CLI flags + +Defaults are sensible, so configuration is optional for onboarding. + +**Example CLI overrides** +```bash +./hostinfo --port 9090 --debug +``` + +## 🧪 Verify Functionality +After running HostInfo by any method, verify that: + +**✔ Web UI is up** + +Visit: `http://localhost:8080` +You should see a system information dashboard. + + +**✔ API responds** +```bash +curl http://localhost:8080/api/v1/info | jq +``` +Expected output (varies per system): +```json +{ + "hostname": "myhost", + "os": "linux", + "cpu": { + "cores": 8 + }, + "memory": "16GB" +} +``` +## 🧹 Cleaning Up (Optional) + +**Docker container cleanup** +```bash +docker ps -a +docker rm +docker rmi hostinfo:latest +``` +**Binary cleanup** +```bash +rm hostinfo +``` + +## 🙋 Need More Details? +Additional documentation is available in: +- `02-installation.md` +- `03-configuration.md` +- `04-usage.md` +- `05-api.md` + +## 🎉 You're Ready! + +You now have HostInfo running — either as a native binary or inside a Docker container. +Next steps depend on your use case: + +➡ For config options → read `03-configuration.md` +➡ For API usage → read `05-api.md` +➡ For development → read `07-development.md` diff --git a/docs/02-installation.md b/docs/02-installation.md new file mode 100644 index 0000000..2cc644b --- /dev/null +++ b/docs/02-installation.md @@ -0,0 +1,213 @@ +# Installation + +This document describes how to install and build the **hostinfo** web application across multiple environments. + +--- + +## 1. 📦 Prerequisites + +Required: +You need the following tools installed: + +| Tool | Purpose | Check | +|---|---|---| +| **Go** `>= 1.21` | Build & run hostinfo | `go version` | +| **Git** | Clone repository | `git --version` | + +Optional but recommended (for deployment / CI): + +- `make` — to use Makefile automation (if present) +- `docker` — for container builds (optional) +- `docker compose` +- `scp` (for remote deployment) +- `systemd` (Linux service) + +## 2. 🔽 Clone Repository + +```bash +git clone https://github.com/MaksymLeus/hostinfo.git +cd hostinfo +``` + +## 3. Backend Dependencies + +```bash +go mod download +``` + +## 4. 🛠️ Local Build Options + +### Option A — Using build.sh (recommended) + +```bash +./build.sh +``` + +This produces a binary based on host OS/architecture, e.g.: +```bash +./bin/hostinfo +``` + +### Option B — Manual go build + +```bash +go build -o ./bin/hostinfo ./cmd/server +``` + +Run it: +```bash +./bin/hostinfo +``` +Access in browser: `http://localhost:8080` + +## 5. Platform-Specific Builds + +### Linux (from macOS/Windows) + +```bash +GOOS=linux GOARCH=amd64 go build -o hostinfo-linux-x64 ./cmd/server +``` +Available arch targets include: + +- `amd64` +- `arm64` + +## 6. ⚙️ Install to GOPATH + +```bash +go install ./cmd/server +``` + +Binary installs into: + +``` +$(go env GOPATH)/bin/hostinfo +``` + +Add GOPATH bin to PATH (if missing): + +```bash +export PATH="$PATH:$(go env GOPATH)/bin" +``` + +## 7. Deployment Targets + +### Linux Server (manual) + +```bash +./build.sh all +scp bin/hostinfo-linux-x64 user@server:/opt/hostinfo/hostinfo +``` + +Run on server: + +```bash +ssh user@server +cd /opt/hostinfo +./hostinfo +``` + +### systemd (optional) + +Create `/etc/systemd/system/hostinfo.service`: + +```ini +[Unit] +Description=Web Hostinfo Service +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/opt/hostinfo +ExecStart=/opt/hostinfo/hostinfo +Restart=on-failure + +[Install] +WantedBy=multi-user.target +``` + +Enable + start: + +```bash +sudo systemctl enable hostinfo +sudo systemctl start hostinfo +``` + +## 8. Docker Installation + +Hostinfo can be containerized using Docker. + +### Build locally: + +```bash +docker build -t hostinfo . -f /docker/Dockerfile +``` + +Run: + +```bash +docker run -p 8080:8080 hostinfo +``` + +Access: + +``` +http://localhost:8080 +``` + +--- + +## 9. Docker Compose + +```bash +docker compose build -f ./docker/docker-compose.yml +docker compose up -d +docker compose logs -f +``` + +Stop: + +```bash +docker compose down +``` + +--- + +## 10. ❗ Troubleshooting + +| Issue | Cause | Resolution | +|---|---|---| +| `go: no such file or directory` | Wrong working directory | `cd hostinfo` | +| `exec format error` | Wrong GOARCH/GOOS | Rebuild with correct target | +| Docker build slow | No layer cache | Enable BuildKit | +| `permission denied` | Missing execute flag | `chmod +x hostinfo` or `chmod +x build.sh` | + +--- + +## 11. 🧼 Uninstallation + +Remove binary: + +```bash +rm -f hostinfo +``` + +Remove build artifacts: + +```bash +rm -rf bin/ +``` + +Remove systemd service: + +```bash +sudo systemctl disable --now hostinfo +sudo rm -f /etc/systemd/system/hostinfo.service +``` + +--- + +## 📄 License + +MIT — see `LICENSE.md` for details. diff --git a/docs/03-usage.md b/docs/03-usage.md new file mode 100644 index 0000000..9a06df4 --- /dev/null +++ b/docs/03-usage.md @@ -0,0 +1,158 @@ +# 04 — Usage + +This document describes how to run, access, and interact with the **hostinfo** application in different environments. + +--- + +## 1. Default Behavior + +When started without arguments, hostinfo: + +- runs an HTTP server on `:8080` +- renders the host information dashboard at `/` + +Example: + +```bash +./hostinfo +``` + +Access in browser: + +``` +http://localhost:8080 +``` + +No configuration files or environment variables are required. + +--- + +## 2. Supported UI Modes + +The dashboard renders the following categories (depending on environment): + +- **Host System** +- **Container Runtime** +- **Cloud Metadata** +- **Network Interfaces** +- **Environment Variables** +- **Runtime Details (Go / OS / Arch)** + +If running inside: + +- Docker → shows container metadata +- AWS EC2 → shows EC2 metadata +- GCP → shows project/zone/machine +- Azure → VM metadata +- Bare-metal → falls back to local host info + +No cloud credentials are needed for metadata detection. + +--- + +## 3. JSON API (If Enabled / Future) + +If JSON output is enabled or supported in the future, examples may include: + +``` +GET /api/info +``` + +Response might include: + +```json +{ + "hostname": "ip-10-0-1-15", + "os": "linux", + "arch": "amd64", + "cloud": "aws", + "region": "us-east-1", + "instanceType": "t3.micro" +} +``` + +> If JSON output is not currently implemented, this section remains reserved for future support. + +--- + +## 4. Environment Behavior + +hostinfo auto-detects cloud/platform context: + +| Platform | Detection | +|---|---| +| Docker | `/proc/self/cgroup`, container hostname | +| AWS EC2 | IMDS (169.254.169.254) | +| GCP | GCP metadata server | +| Azure | Azure Metadata Service | +| Local | no metadata endpoints | + +All metadata requests are **safe**: +- timeouts are short +- failures do not crash the app + +--- + +## 5. Port Configuration (If Provided) + +If port configuration is supported later, recommended format: + +```bash +./hostinfo --port 9090 +``` + +Access: + +``` +http://localhost:9090 +``` + +If not yet implemented, this section becomes future reserved. + +--- + +## 6. Logging + +hostinfo prints startup info to stdout: + +Example: + +``` +[hostinfo] starting server on :8080 +[hostinfo] environment: docker +[hostinfo] cloud: aws (us-east-1) +``` + +Logs can be collected via: + +- systemd (`journalctl`) +- Docker logs +- Kubernetes logs + +--- + +## 7. Production Notes + +When running in production: + +- run behind reverse proxy (nginx, traefik, caddy) +- restrict public access unless required +- enable TLS termination on proxy layer +- do not expose raw port `8080` to internet without TLS + +--- + +## 8. Troubleshooting + +| Issue | Cause | Resolution | +|---|---|---| +| Blank UI | Cloud metadata timeout | Wait ~1s or disable metadata calls | +| `bind: address already in use` | Port in use | Change port or stop other service | +| Docker no metadata | No cloud server | Expected inside local docker runs | +| Cloud info missing | Not running on cloud | Expected fallback | + +--- + +## License + +MIT — see `LICENSE.md`. diff --git a/docs/05-architecture.md b/docs/05-architecture.md new file mode 100644 index 0000000..abf175d --- /dev/null +++ b/docs/05-architecture.md @@ -0,0 +1,239 @@ +# 05 — Architecture + +This document outlines the architecture, execution model, core components, and cloud/container detection logic of the **hostinfo** application. + +--- + +## 1. High-Level Overview + +hostinfo is a **single-binary Go application** that: + +- collects host/container/cloud metadata +- aggregates system + network information +- renders a web dashboard using templates +- performs safe non-blocking cloud metadata probing + +No external services or databases are required. + +--- + +## 2. Process Model + +``` +┌─────────────────────────┐ +│ hostinfo (Go binary) │ +│ │ +│ ┌───────────────────┐ │ +│ │ HTTP Server │<── incoming web requests +│ └───────────────────┘ │ +│ ┌───────────────────┐ │ +│ │ Metadata Collect. │── system, runtime, env +│ └───────────────────┘ │ +│ ┌───────────────────┐ │ +│ │ Cloud Detection │── AWS/GCP/Azure/local +│ └───────────────────┘ │ +│ ┌───────────────────┐ │ +│ │ Template Engine │── render HTML dashboard +│ └───────────────────┘ │ +└─────────────────────────┘ +``` + +All components run in-process, no goroutine explosions, no IPC. + +--- + +## 3. Source Code Layout + +Relevant structure (simplified): + +``` +cmd/server/ → main entrypoint + HTTP bootstrap +internal/ → metadata collectors & helpers +web/templates/ → HTML layout for dashboard +web/image.png → UI static asset +docker/ → Docker + compose definitions +.github/ → CI/CD workflows +``` + +Key directories are kept minimal to preserve clarity. + +--- + +## 4. Component Breakdown + +### 4.1 HTTP Server + +Responsibilities: + +- listen on `:8080` (default) +- serve dashboard at `/` +- render templates with collected data + +### 4.2 Metadata Collector + +Aggregates: + +- hostname +- OS / arch / runtime +- environment variables +- uptime + process info +- network interfaces +- container markers +- cloud metadata (if available) + +All calls are **non-blocking** with short timeouts. + +### 4.3 Cloud Detection Layer + +Checks metadata endpoints safely: + +| Provider | Method | +|---|---| +| AWS | IMDSv1 (169.254.169.254) | +| GCP | metadata.google.internal | +| Azure | 169.254.169.254/metadata | +| Docker | cgroup/hostname heuristics | +| Local | fallback if no cloud metadata | + +Failures never crash — they degrade to `local`. + +### 4.4 Template Rendering + +Backend populates a structured template context: + +```go +type DashboardData struct { + Host HostInfo + Container ContainerInfo + Cloud CloudInfo + Network []NetInterface + Runtime GoRuntime + Env map[string]string +} +``` + +Rendered via `index.html` under `web/templates`. + +--- + +## 5. Runtime Behavior + +Startup sequence: + +1. initialize collectors +2. detect execution environment +3. probe cloud metadata asynchronously +4. serve HTTP dashboard +5. update metrics on each request + +### Execution Contexts + +| Context | Behavior | +|---|---| +| Local bare-metal | fallback mode | +| Docker container | container info enabled | +| AWS EC2 | IMDS metadata | +| GCP | metadata server | +| Azure | IMDS metadata | +| Unknown | fallback | + +Timeout defaults are small to avoid UI blocking. + +--- + +## 6. Cloud & Container Detection Logic + +Detection pipeline (simplified): + +``` +Container? → Check cgroup / hostname patterns + ↓ yes + Docker + +Cloud? → Probe IMDS endpoints + ↓ yes + AWS / GCP / Azure + +Else → Local fallback +``` + +Priority order: + +``` +Container → Cloud → Local +``` + +Cloud checks run in parallel to reduce latency. + +--- + +## 7. Dependencies + +### Language & Runtime + +- **Go >= 1.21** + +### External Services + +None required. + +### External Libraries + +Minimal standard library usage preferred to keep binary small and portable. + +--- + +## 8. Deployment Characteristics + +- **stateless** (no DB, no local storage) +- **horizontal scale-friendly** (idempotent) +- **portable** across bare-metal, VMs, containers +- **read-only** by design (never mutates system) + +Ideal for debugging ephemeral infra, CI workers, cloud VMs, or container clusters. + +--- + +## 9. Security Considerations + +hostinfo: + +- does **not** require cloud credentials +- does **not** modify system state +- does **not** open outbound ports besides metadata probes +- does **not** expose OS secrets beyond env vars + +Exposure risks: + +- environment variables may contain secrets in certain deployments +- dashboard should not be public-facing without TLS/proxy + +Recommendation: + +Deploy behind Nginx / Traefik / Caddy for TLS + authentication. + +--- + +## 10. Extensibility + +Possible future modules: + +- JSON API (`/api/info`) +- Prometheus `/metrics` +- CLI flags (`--port`, `--json`) +- WASM UI bundle +- Plugin architecture + +Backend design keeps boundaries flexible: + +``` +Collector → Adapter → Renderer +``` + +No global state except basic runtime caches. + +--- + +## License + +MIT — see `LICENSE.md`. diff --git a/docs/06-deployment.md b/docs/06-deployment.md new file mode 100644 index 0000000..b9132db --- /dev/null +++ b/docs/06-deployment.md @@ -0,0 +1,293 @@ +# Deployment + +This document describes available deployment strategies for the **hostinfo** web application, including bare-metal, Docker, Docker Compose, systemd, and cloud environments. + +--- + +## 1. Deployment Models + +hostinfo supports: + +- **Bare-metal binary** (Linux/Windows/macOS) +- **Docker container** +- **Docker Compose** +- **systemd** service +- **Cloud VMs** (AWS EC2, GCP Compute, Azure VM) +- (Optional) **Kubernetes** via container image + +hostinfo is stateless: +- No external dependencies +- No persistent storage required +- No configuration files required by default + +--- + +## 2. Deployment Preparation + +### Clone and build: + +```bash +git clone https://github.com/MaksymLeus/hostinfo.git +cd hostinfo +./build.sh all # or: go build -o hostinfo ./cmd/server +``` + +Produced binaries are typically placed in `bin/`. + +--- + +## 3. Bare-Metal Deployment (Linux) + +### 3.1 Upload binary to server + +```bash +scp bin/hostinfo-linux-x64 user@server:/opt/hostinfo/hostinfo +``` + +### 3.2 Start manually + +```bash +ssh user@server +cd /opt/hostinfo +chmod +x hostinfo +./hostinfo +``` + +Service runs on: + +``` +http://:8080 +``` + +--- + +## 4. systemd Service (Linux) + +### 4.1 Create unit file + +`/etc/systemd/system/hostinfo.service`: + +```ini +[Unit] +Description=Hostinfo Web Service +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/opt/hostinfo +ExecStart=/opt/hostinfo/hostinfo +Restart=on-failure +RestartSec=3 + +[Install] +WantedBy=multi-user.target +``` + +### 4.2 Enable & start + +```bash +sudo systemctl enable hostinfo +sudo systemctl start hostinfo +``` + +### 4.3 Check status and logs + +```bash +sudo systemctl status hostinfo +sudo journalctl -u hostinfo -f +``` + +--- + +## 5. Docker Deployment + +### 5.1 Build image locally + +```bash +docker build -t hostinfo . +``` + +### 5.2 Run container + +```bash +docker run -p 8080:8080 hostinfo +``` + +Access: + +``` +http://localhost:8080 +``` + +--- + +## 6. Docker Compose Deployment + +`docker-compose.yml` (existing in project): + +```bash +docker compose build +docker compose up -d +``` + +Manage lifecycle: + +```bash +docker compose logs -f +docker compose down +``` + +--- + +## 7. Cloud VM Deployment + +### Supported environments (no extra config): + +- AWS EC2 +- GCP Compute Engine +- Azure VM + +hostinfo auto-detects cloud metadata **without** credentials. + +#### Example Ubuntu VM deployment: + +```bash +# build binary locally +./build.sh all + +# copy to VM +scp bin/hostinfo-linux-x64 ubuntu@:/opt/hostinfo/hostinfo + +# run on VM +ssh ubuntu@ +cd /opt/hostinfo +./hostinfo +``` + +### Firewall considerations: + +- AWS EC2 → Security Group: allow inbound TCP 8080 +- GCP Compute → Firewall Rule: allow TCP 8080 +- Azure NSG → allow TCP 8080 + +--- + +## 8. Reverse Proxy (Optional) + +For production HTTPS, recommend: + +- **Nginx** +- **Caddy** +- **Traefik** + +Example nginx site config: + +``` +location / { + proxy_pass http://localhost:8080; +} +``` + +Enable TLS via Let's Encrypt or Traefik. + +--- + +## 9. Kubernetes (Optional) + +If deployed to Kubernetes, use the existing Docker image: + +Example minimal manifest: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hostinfo +spec: + replicas: 1 + selector: + matchLabels: + app: hostinfo + template: + metadata: + labels: + app: hostinfo + spec: + containers: + - name: hostinfo + image: maximleus/hostinfo:latest + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: hostinfo +spec: + type: ClusterIP + selector: + app: hostinfo + ports: + - port: 80 + targetPort: 8080 +``` + +Ingress optional depending on cluster. + +--- + +## 10. Health & Monitoring + +hostinfo exposes a UI at: + +``` +/ (dashboard) +``` + +If you later add JSON endpoints (e.g., `/api/info`), update this section. + +--- + +## 11. Updates & Rollbacks + +### Bare-Metal: + +```bash +systemctl stop hostinfo +# replace binary +systemctl start hostinfo +``` + +### Docker: + +```bash +docker pull maximleus/hostinfo:latest +docker compose up -d +``` + +### Kubernetes: + +```bash +kubectl rollout restart deployment/hostinfo +``` + +Rollbacks via ReplicaSet history. + +--- + +## 12. Deployment Recommendations + +For production: + +- Run behind reverse proxy with HTTPS +- Use systemd for bare-metal +- Use Docker or Kubernetes for cloud +- Prefer immutable container deployments +- Do not expose port 8080 directly to public internet without TLS + +--- + +## License + +MIT — see `LICENSE.md`. diff --git a/docs/07-cloud-detection.md b/docs/07-cloud-detection.md new file mode 100644 index 0000000..b76a91d --- /dev/null +++ b/docs/07-cloud-detection.md @@ -0,0 +1,178 @@ +# 06 — Cloud Detection + +hostinfo automatically detects cloud environments (AWS, GCP, Azure) and gathers metadata **without requiring credentials**. +This document explains the detection logic, endpoints, timeouts, and fallback behavior. + +--- + +## 1. Overview + +Cloud detection is: + +- **Safe**: read-only, short timeouts +- **Non-blocking**: asynchronous or concurrent calls +- **Fallback-aware**: defaults to local host if no cloud detected +- **Environment-agnostic**: works in containers, bare-metal, and VMs + +Detection order: + +``` +1. Container detection +2. Cloud detection +3. Local fallback +``` + +--- + +## 2. Container Detection (Precedes Cloud) + +hostinfo first determines if it is running inside a container: + +- **Docker / Podman / LXC** detection: + - Check `/proc/self/cgroup` for container patterns + - Check `/proc/1/cgroup` for docker identifiers + - Inspect hostname for container hashes + +If container detected: + +- Adds container uptime +- Marks environment as `container` +- Continues to cloud detection if available + +--- + +## 3. AWS Detection + +AWS metadata is fetched from **Instance Metadata Service (IMDS)**: + +- Endpoint: `http://169.254.169.254/latest/meta-data/` +- Information retrieved: + - Instance ID + - Region & Availability Zone + - Instance type + - Private IP +- Requests are **short timeout** (~500ms) +- Failures do **not crash** the application + +Example metadata keys: + +``` +instance-id +instance-type +placement/availability-zone +local-ipv4 +``` + +--- + +## 4. Google Cloud Detection + +GCP metadata service: + +- Endpoint: `http://metadata.google.internal/computeMetadata/v1/` +- Requires header: `Metadata-Flavor: Google` +- Retrieves: + - Project ID + - Zone + - Machine type +- Timeout is short (~500ms) +- Failure → fallback + +Example: + +```bash +curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/id +``` + +--- + +## 5. Azure Detection + +Azure Instance Metadata Service (IMDS): + +- Endpoint: `http://169.254.169.254/metadata/instance?api-version=2021-02-01` +- Header: `Metadata: true` +- Retrieves: + - VM ID + - Location + - VM size/type +- Timeout: short (~500ms) +- Failures do **not crash** app + +Example: + +```bash +curl -H "Metadata:true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" +``` + +--- + +## 6. Local / Fallback + +If none of the cloud endpoints respond: + +- Environment marked as `local` +- Only host and container info is shown +- Ensures dashboard always displays minimal info + +--- + +## 7. Metadata Caching + +- Metadata is cached in memory per process +- Avoids repeated network calls on each HTTP request +- Cache invalidated on restart +- No persistence to disk + +--- + +## 8. Parallel Probing Logic + +Detection sequence (simplified): + +``` +start -> container check + -> launch cloud probes concurrently (AWS, GCP, Azure) + -> wait max 500ms per probe + -> first successful response sets cloud type + -> others cancel + -> fallback to local if all fail +``` + +This prevents blocking the HTTP server. + +--- + +## 9. Security Considerations + +- No credentials required +- Only public metadata endpoints are accessed +- Cloud detection is **read-only** +- Sensitive values (API keys, secrets) are **never accessed** +- Failures are silent and logged optionally + +--- + +## 10. Deployment Notes + +- Works inside containers → detects container + cloud host if present +- Works in ephemeral CI/CD runners → falls back gracefully +- Works in multi-cloud hybrid setups → first responding cloud is chosen +- Safe for production → no network egress besides metadata endpoints + +--- + +## 11. Troubleshooting + +| Issue | Cause | Resolution | +|---|---|---| +| Cloud metadata not showing | Not running on cloud | Expected behavior, fallback to local | +| Dashboard slow | Cloud metadata timed out | Network latency; consider increasing timeout if desired | +| Metadata endpoint blocked | Firewall or VPC rules | Ensure access to IMDS (AWS/Azure) or metadata server (GCP) | +| Running inside container only | Container detected but cloud unavailable | Expected; container may not have cloud metadata access | + +--- + +## License + +MIT — see `LICENSE.md`. diff --git a/docs/08-api.md b/docs/08-api.md new file mode 100644 index 0000000..f38767a --- /dev/null +++ b/docs/08-api.md @@ -0,0 +1,206 @@ +# 07 — API + +This document defines the **hostinfo HTTP API** for programmatic access. +The API complements the web dashboard by exposing host, container, and cloud information as JSON. + +--- + +## 1. Overview + +- Base URL: `/` +- Current default: **dashboard HTML** +- Future JSON endpoints follow `/api/v1/...` +- Stateless and read-only +- No authentication required by default + +> All endpoints are optional; failures degrade gracefully. + +--- + +## 2. API Versioning + +Planned versioning: + +``` +/api/v1/ +``` + +- `v1` = initial stable API +- Future versions: `v2`, `v3` for extensions or breaking changes + +--- + +## 3. Endpoints + +### 3.1 `/api/v1/info` + +Returns a complete snapshot of the current host, container, and cloud metadata. + +**Method:** `GET` +**Response (JSON):** + +```json +{ + "host": { + "hostname": "my-host", + "os": "linux", + "arch": "amd64", + "uptime": "5h32m", + "goVersion": "go1.24" + }, + "container": { + "id": "a1b2c3d4", + "runtime": "docker", + "uptime": "5h30m" + }, + "cloud": { + "provider": "aws", + "region": "us-east-1", + "availabilityZone": "us-east-1a", + "instanceType": "t3.micro", + "instanceId": "i-1234567890abcdef" + }, + "network": [ + { + "name": "eth0", + "mac": "02:42:ac:11:00:02", + "ips": ["172.17.0.2"] + } + ], + "env": { + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOSTINFO_ENV": "production" + } +} +``` + +--- + +### 3.2 `/api/v1/health` + +Returns server health and uptime. + +**Method:** `GET` +**Response:** + +```json +{ + "status": "ok", + "uptime": "5h32m", + "version": "1.0.0", + "env": "production" +} +``` + +- Useful for monitoring / readiness probes +- Can be extended for liveness probes + +--- + +### 3.3 Future Endpoints + +| Endpoint | Description | +|----------|------------| +| `/api/v1/metrics` | Prometheus-compatible metrics | +| `/api/v1/cloud` | Cloud metadata only | +| `/api/v1/container` | Container info only | +| `/api/v1/network` | Network interfaces and IPs | + +--- + +## 4. Request/Response Conventions + +- All requests: `GET` +- JSON only +- Response content-type: + +```http +Content-Type: application/json; charset=utf-8 +``` + +- Errors use standard HTTP codes: + +| Code | Meaning | +|------|--------| +| 200 | Success | +| 400 | Bad request | +| 404 | Endpoint not found | +| 500 | Internal server error | + +Example error: + +```json +{ + "error": "cloud metadata not available" +} +``` + +--- + +## 5. Query Parameters (Future) + +Potential optional query parameters: + +- `?format=json` → force JSON response from dashboard +- `?fields=host,cloud` → return selected sections only +- `?timeout=500ms` → adjust metadata probe timeout + +--- + +## 6. Security Considerations + +- No authentication required by default +- Exposing environment variables may leak secrets +- Recommended to run behind reverse proxy with HTTPS and optional auth +- Ensure cloud metadata endpoints are internal-only when exposing API externally + +--- + +## 7. Versioning & Deprecation + +- All endpoints must specify API version in path (`/api/v1/...`) +- Future breaking changes increment version number (`v2`, `v3`) +- Deprecated endpoints respond with HTTP `410 Gone` and redirect message + +--- + +## 8. Rate Limiting & Performance + +- Currently **no rate limiting** +- Consider implementing per-IP throttling if exposed publicly +- Metadata probes are cached in-memory for performance +- Requests are handled asynchronously to prevent blocking + +--- + +## 9. Example Usage + +### curl Example + +```bash +curl http://localhost:8080/api/v1/info +``` + +### Python Example + +```python +import requests + +resp = requests.get("http://localhost:8080/api/v1/info") +data = resp.json() +print(data["cloud"]["provider"]) +``` + +--- + +## 10. Logging + +- API access logs can be enabled +- Standard stdout logging +- No persistent storage + +--- + +## License + +MIT — see `LICENSE.md`. diff --git a/docs/09-releasing.md b/docs/09-releasing.md new file mode 100644 index 0000000..02b8bb2 --- /dev/null +++ b/docs/09-releasing.md @@ -0,0 +1,174 @@ +# 09 — Releasing + +This document describes the **release process** for the **hostinfo** web application, including versioning, Git workflow, CI/CD, and Docker publishing. + +--- + +## 1. Versioning Strategy + +hostinfo follows **Semantic Versioning** (SemVer): + +``` +MAJOR.MINOR.PATCH +``` + +- **MAJOR**: breaking changes +- **MINOR**: new features, backward-compatible +- **PATCH**: bug fixes, documentation, small improvements + +Example: + +``` +v1.3.0 → new feature added +v1.3.1 → bug fix +v2.0.0 → breaking change +``` + +--- + +## 2. Git Workflow + +1. **Main branch**: `main` + - Stable production-ready code +2. **Feature branches**: `feature/` + - For new features +3. **Hotfix branches**: `hotfix/` + - Critical bug fixes +4. **Release branches** (optional): `release/` + +Commit messages follow **Conventional Commits**: + +- `feat:` → new feature +- `fix:` → bug fix +- `chore:` → maintenance +- `docs:` → documentation update +- `refactor:` → code refactor +- `test:` → tests only +- `perf:` → performance improvement + +This ensures **automatic versioning** during release. + +--- + +## 3. Pre-Release Checklist + +Before creating a release: + +- ✅ All tests pass (`go test ./...`) +- ✅ Linting passed (`go vet ./...`, `golangci-lint`) +- ✅ Documentation updated +- ✅ Docker image builds successfully +- ✅ Changelog updated (if manual) + +--- + +## 4. Semantic Release + +The project uses **GitHub Actions + semantic release**: + +- Workflow file: `.github/workflows/template-semantic-release.yml` +- Automatically determines next version from commit messages +- Creates GitHub release with changelog +- Tags release in Git + +### 4.1 Triggering Release + +Releases are triggered automatically on: + +- `push` to `main` +- Manually via GitHub Actions `Run workflow` button + +### 4.2 Example Commands (Local Dry-Run) + +```bash +# Install semantic release CLI +npm install -g semantic-release @semantic-release/git + +# Dry run +semantic-release --dry-run +``` + +This previews the next version and changelog without committing. + +--- + +## 5. Docker Image Release + +hostinfo Docker images are published to Docker Hub: + +- **Registry**: [`maximleus/hostinfo`](https://hub.docker.com/r/maximleus/hostinfo) +- Multi-arch: `linux/amd64`, `linux/arm64` +- Base image: `golang:1.24-alpine` + +### 5.1 Automated Build + +GitHub Actions workflow: `.github/workflows/template-docker.yml` + +- Builds Docker image on each release +- Tags image with SemVer version and `latest` +- Pushes image to Docker Hub + +Example Docker tags: + +``` +maximleus/hostinfo:1.2.3 +maximleus/hostinfo:latest +``` + +### 5.2 Manual Build & Push + +```bash +# Build image +docker build -t hostinfo . + +# Tag with version +docker tag hostinfo maximleus/hostinfo:1.2.3 + +# Push to Docker Hub +docker push maximleus/hostinfo:1.2.3 +docker push maximleus/hostinfo:latest +``` + +--- + +## 6. Release Notes + +- Generated automatically from commit messages +- Included in GitHub release +- Contains: + - Features + - Bug fixes + - Chores +- Optional: additional manual notes + +--- + +## 7. Rollback Strategy + +- Docker: redeploy previous image (`docker tag ...`) +- Bare-metal: keep previous binary copy +- systemd: restart previous binary +- Kubernetes: rollback via `kubectl rollout undo deployment/hostinfo` + +--- + +## 8. Post-Release Checklist + +- ✅ Verify dashboard on production +- ✅ Confirm Docker image works +- ✅ Confirm semantic release tags pushed +- ✅ Update documentation references + +--- + +## 9. References + +- [Semantic Release](https://semantic-release.gitbook.io/) +- [Conventional Commits](https://www.conventionalcommits.org/) +- [Docker Hub](https://hub.docker.com/r/maximleus/hostinfo) + +--- + +## License + +MIT — see `LICENSE.md`. diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000..34e9365 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,60 @@ +# Add Kubernetes +### ☸️ Kubernetes (NO RBAC) +Uses **Downward API only** +- Pod name +- Namespace +- Pod IP +- Node name +- Service account +- Container name + +If you later deploy to K8s, probes map directly: +```yaml +livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + +readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 3 + periodSeconds: 5 +``` + +# Upgrade UI +- Make new ui on js +- favicon.ico + + +# CI +- Docker cache +- speed up CI + +Scenario B — .releaserc stored in another repo + +Not supported out of the box, because semantic-release always evaluates config against the current working directory (current repo codebase). + +BUT you can make it work by: + +Option 1 — Checkout config repo first + +In workflow: +```yaml +- uses: actions/checkout@v4 + with: + path: current + +- uses: actions/checkout@v4 + with: + repository: myorg/semantic-config + path: semantic-config + +- name: Use external .releaserc + run: cp semantic-config/.releaserc current/.releaserc +``` + +This approach is used in mono-repos and org-wide standards. \ No newline at end of file diff --git a/docs/semantic.md b/docs/semantic.md new file mode 100644 index 0000000..9628578 --- /dev/null +++ b/docs/semantic.md @@ -0,0 +1,128 @@ +# Semantic Release Guide + +This document explains how to use Semantic Release in this repository to automatically manage versioning, changelogs, and GitHub releases. + + +## Table of Contents +- [Semantic Release Guide](#semantic-release-guide) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [How It Works](#how-it-works) + - [Commit message examples:](#commit-message-examples) + - [Configuration](#configuration) + - [Key options:](#key-options) + - [Workflow Usage](#workflow-usage) + - [Inputs:](#inputs) + - [Secrets:](#secrets) + - [Custom Versioning](#custom-versioning) + - [Troubleshooting](#troubleshooting) + + + +### Overview + +Semantic Release automates versioning and releases based on commit messages following Conventional Commits +. + +- Version is automatically incremented according to commit type: + + - `feat`: Minor version bump + - `fix`: Patch version bump + - `BREAKING` CHANGE: Major version bump + +- Changelog is automatically generated. +- GitHub release is created automatically with the generated notes. +- Optional npm publish (if Node.js project). + + +### How It Works + +1. Analyze commits since the last release. +2. Determine the next semantic version. +3. Update `CHANGELOG.md`. +4. Commit changes (if configured). +5. Create a GitHub release with the new version. + +### Commit message examples: +``` +feat: add healthcheck endpoint +fix: resolve crash on startup +feat!: remove old API endpoint (BREAKING CHANGE) +``` + +### Configuration + +The repository uses `.releaserc.yaml `for configuration: +```yaml +branches: + - main + - "fs/pl/*" + - "+([0-9])?(.{+([0-9]),x}).x" + +plugins: + - "@semantic-release/commit-analyzer" + - "@semantic-release/release-notes-generator" + - "@semantic-release/changelog" + - "@semantic-release/git" + - "@semantic-release/github" +``` +### Key options: + +- `branches`: Defines release branches. Tags are only created on these branches. + +- `plugins`: Control which steps run (analyze commits, generate notes, update changelog, push changes, publish to GitHub). + +Note: For a pure Go project, npm publishing steps can be skipped. Only GitHub release is needed. + +### Workflow Usage + +The GitHub Actions workflow is configured as a reusable workflow: +```yaml +jobs: + release: + uses: ./.github/workflows/template-semantic-release.yml + with: + dry_run: false + semantic_version: 20 + secrets: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +``` +### Inputs: +| Input Name | Description | +| -------- | ------- | +| `dry_run` | `true` → simulate release without pushing tags. `false` → create actual release. | +| `semantic_version` | Version of semantic-release to use (optional, default managed internally). | + +### Secrets: +| Secret Name | Description | +| -------- | ------- | +| `GITHUB_TOKEN` | Required for GitHub API authentication and pushing tags. | + +### Custom Versioning + +Semantic Release automatically determines the next version based on commit messages: + +`fix`: → patch (`1.0.1`) + +`feat:` → minor (`1.1.0`) + +`BREAKING CHANGE` → major (`2.0.0`) + +You can override versioning temporarily using `inputs.semantic_version` in the workflow. + +### Troubleshooting + +1) Permission errors pushing tags: + + - Ensure `GITHUB_TOKEN` is set in workflow secrets. + + - Workflow must run on allowed branches only. + +2) Invalid release branch error: + + - Ensure your branch is listed in `branches` in `.releaserc.yaml`. + +3) Dry run vs actual run: + + - Dry run does not push or tag. Set `dry_run: false` to perform a real release. \ No newline at end of file diff --git a/docs/test.md b/docs/test.md new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4d231e1 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module hostinfo + +go 1.20 diff --git a/internal/favicon_io/android-chrome-192x192.png b/internal/favicon_io/android-chrome-192x192.png new file mode 100644 index 0000000..1e59e91 Binary files /dev/null and b/internal/favicon_io/android-chrome-192x192.png differ diff --git a/internal/favicon_io/android-chrome-512x512.png b/internal/favicon_io/android-chrome-512x512.png new file mode 100644 index 0000000..06f6d97 Binary files /dev/null and b/internal/favicon_io/android-chrome-512x512.png differ diff --git a/internal/favicon_io/apple-touch-icon.png b/internal/favicon_io/apple-touch-icon.png new file mode 100644 index 0000000..006a1d9 Binary files /dev/null and b/internal/favicon_io/apple-touch-icon.png differ diff --git a/internal/favicon_io/favicon-16x16.png b/internal/favicon_io/favicon-16x16.png new file mode 100644 index 0000000..14eb75b Binary files /dev/null and b/internal/favicon_io/favicon-16x16.png differ diff --git a/internal/favicon_io/favicon-32x32.png b/internal/favicon_io/favicon-32x32.png new file mode 100644 index 0000000..3537def Binary files /dev/null and b/internal/favicon_io/favicon-32x32.png differ diff --git a/internal/favicon_io/favicon.ico b/internal/favicon_io/favicon.ico new file mode 100644 index 0000000..2e87bec Binary files /dev/null and b/internal/favicon_io/favicon.ico differ diff --git a/new-readme.md b/new-readme.md new file mode 100644 index 0000000..dd227ab --- /dev/null +++ b/new-readme.md @@ -0,0 +1,183 @@ +# 🖥️ HostInfo + +

+ release + ci status + license + coverage + docker pulls + semantic-release +

+ +**HostInfo** is a lightweight Go service that exposes server/system information through both an HTML interface and a RESTful JSON API. +It’s designed for DevOps/debugging scenarios, observability dashboards, and automation. + +--- + +## ✨ Features + +- 🚀 Fast & lightweight Go server +- 🌐 Simple web UI +- 📡 JSON API support +- 🐳 Docker & Compose ready +- 🔒 Zero external dependencies +- 📦 CI/CD & Semantic Release compatible +- 📁 Clean repo & docs structure + +--- + +## 📦 Installation + +### Option A — Local Build (Go) + +```bash +git clone https://github.com/youruser/hostinfo.git +cd hostinfo +go build ./cmd/server +./server +``` + +### Option B — Docker +```bash +docker build -t hostinfo:latest ./docker +docker run -p 8080:8080 hostinfo:latest +``` + +### Option C — Docker Compose +```bash +docker compose up -d +``` + +## 🏃 Getting Started +```bash +./server +``` +Open in browser: `http://localhost:8080` + +You’ll see system info like CPU, RAM, OS, disk, hostname, etc. + +## 🧰 Usage +### Web UI + +Provides a human-friendly view of system info. + +### JSON API +```http +GET /api/v1/info +``` +### Sample response: +```http +{ + "hostname": "mylaptop", + "cpu": { + "cores": 8, + "model": "Intel(R) Core(TM) i7" + }, + "memory": "16GB", + "os": "Linux" +} +``` +## ⚙️ Configuration + +### Environment variables: + +| Variable | Default | Description | +|:---|:---:|:---| +| `PORT` | `8080` | HTTP port | +| `DEBUG` | `false` | Debug logs | + +### CLI flags: + +```bash +./server --port 9090 --debug +``` + +## 🧱 Repository Structure + +```bash +hostinfo +├── cmd/server +├── internal +├── web +├── docker +├── docs +├── .github/workflows +├── scripts/hooks +├── tools +└── ... +``` +For full breakdown, see: [`docs/00-overview.md`](docs/00-overview.md) + +## ⚙️ Development + +### Prerequisites: +- Go 1.22+ +- Docker (optional) +- Linux/macOS/Windows + +### Run Tests +```bash +go test ./... +``` +### Git Hooks (pre-commit & commit-msg) +```bash +./tools/setup-hooks.sh +``` +## 🚀 CI/CD & Releases +This project supports: + +✔ GitHub Actions CI + +✔ Docker image builds + +✔ Semantic Versioning + +✔ Automated changelog generation + +Semantic Release is used for tagging & changelog: + +- feat: → minor version +- fix: → patch version +- BREAKING CHANGE: → major version + +## 🐳 Docker Image +When published, you’ll be able to pull: +```bash +docker pull maksymleus/hostinfo:latest +``` +Or run: +```bash +docker run -p 8080:8080 youruser/hostinfo:latest +``` +## 📚 Documentation +See the docs/ folder for: +- [Overview](./docs/00-overview.md) +- [Getting Started](./docs/01-getting-started.md) +- Installation +- API Reference +- CI/CD +- Deployment +- Releasing + +## 🤝 Contributing +Contributions are welcome! + +Steps: + +1. Fork the project +2. Create your feature branch +3. Commit using Conventional Commits +4. Push & open PR + +Please follow: [`docs/07-development.md`](docs/07-development.md) + +## 📄 License +This project is licensed under the MIT License — see [`LICENSE.md`](LICENSE.md) for details. + +## ⭐ Support + +If you find this project helpful: +- Star the repo +- Open issues or feature requests +- Share with others + diff --git a/scripts/hooks/commit-msg b/scripts/hooks/commit-msg new file mode 100755 index 0000000..b24b637 --- /dev/null +++ b/scripts/hooks/commit-msg @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +commit_msg_file="$1" +commit_msg=$(head -n1 "$commit_msg_file") + +# Conventional commit regex +pattern="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9_-]+\))?: .+" + +if [[ ! $commit_msg =~ $pattern ]]; then + echo "❌ Commit message does not follow Conventional Commits!" + echo "" + echo "Examples:" + echo " feat: add host info endpoint" + echo " fix(server): panic on nil pointer" + echo " docs: update README" + echo " chore: bump dependencies" + echo "" + echo "Your commit message:" + echo " $commit_msg" + echo "" + exit 1 +fi + +echo "✔ Commit message format OK." diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100755 index 0000000..2e24371 --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -e + +echo "➡ Running go fmt..." +if [ -n "$(gofmt -l .)" ]; then + echo "❌ go fmt failed. Fix formatting first." + gofmt -l . + exit 1 +fi + +echo "➡ Running go vet..." +go vet ./... + +if command -v staticcheck >/dev/null 2>&1; then + echo "➡ Running staticcheck..." + staticcheck ./... +fi + +# echo "➡ Running go build..." +# go build ./... + +# # Optional docker build test +# if docker version >/dev/null 2>&1; then +# echo "➡ Checking Dockerfile builds..." +# docker build -q -f docker/Dockerfile . +# fi + +echo "✔ Pre-commit checks passed." diff --git a/tools/build.sh b/tools/build.sh new file mode 100644 index 0000000..03916bb --- /dev/null +++ b/tools/build.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Build script for hostinfo +# Builds binaries for Linux x64, Mac OS (Intel and Apple Silicon) +# Usage: +# ./build.sh - Build hostinfo binary in root directory for quick testing +# ./build.sh all - Build all platform binaries and store in bin/ directory + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +APP_NAME="hostinfo" +VERSION="1.0.0" +BUILD_DIR="bin" + +echo -e "${GREEN}Building Host Info v${VERSION}${NC}" + +# Function to build for a specific platform +build_platform() { + local GOOS=$1 + local GOARCH=$2 + local OUTPUT=$3 + + echo -e "${YELLOW}Building for ${GOOS}/${GOARCH}...${NC}" + GOOS=$GOOS GOARCH=$GOARCH go build -o "$OUTPUT" -ldflags="-s -w" ./cmd/server/hostinfo.go + echo -e "${GREEN}Built: ${OUTPUT}${NC}" +} + +# If no arguments or "quick" argument, build for current platform in root directory +if [ $# -eq 0 ]; then + echo -e "${YELLOW}Building quick test binary...${NC}" + go build -o hostinfo ./cmd/server/hostinfo.go + echo -e "${GREEN}Build complete! Run with: ./hostinfo${NC}" + exit 0 +fi + +# Build all platforms +if [ "$1" == "all" ]; then + # Create bin directory if it doesn't exist + mkdir -p "$BUILD_DIR" + + # Build for different platforms + build_platform "linux" "amd64" "${BUILD_DIR}/${APP_NAME}-linux-x64" + build_platform "darwin" "amd64" "${BUILD_DIR}/${APP_NAME}-darwin-intel" + build_platform "darwin" "arm64" "${BUILD_DIR}/${APP_NAME}-darwin-arm64" + + echo "" + echo -e "${GREEN}All builds completed successfully!${NC}" + echo -e "${GREEN}Binaries are in the ${BUILD_DIR}/ directory:${NC}" + ls -lh ${BUILD_DIR}/ + exit 0 +fi + +echo -e "${RED}Invalid argument. Use './build.sh' for quick build or './build.sh all' for all platforms${NC}" +exit 1 \ No newline at end of file diff --git a/tools/setup-hooks.sh b/tools/setup-hooks.sh new file mode 100755 index 0000000..cf3b982 --- /dev/null +++ b/tools/setup-hooks.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +git config core.hooksPath scripts/hooks +echo "✔ Git hooks installed via core.hooksPath" +echo "Current hooksPath: $(git config core.hooksPath)" + diff --git a/web/image.png b/web/image.png new file mode 100644 index 0000000..06e706d Binary files /dev/null and b/web/image.png differ diff --git a/web/templates/index.html b/web/templates/index.html new file mode 100644 index 0000000..4b4df4d --- /dev/null +++ b/web/templates/index.html @@ -0,0 +1,95 @@ + + + + Docker Host Info + + + + +
+

🚀 Docker Container Host Info

+ +
+

☁️ Cloud

+
    +
  • Provider: {{.Cloud.Provider}}
  • + {{if .Cloud.Instance}}
  • Instance: {{.Cloud.Instance}}
  • {{end}} + {{if .Cloud.Region}}
  • Region: {{.Cloud.Region}}
  • {{end}} + {{if .Cloud.Zone}}
  • Zone: {{.Cloud.Zone}}
  • {{end}} + + {{range $k, $v := .Cloud.Extra}} +
  • {{$k}}: {{$v}}
  • + {{end}} +
+
+ +
+

🖥 System

+
    +
  • Hostname: {{.Hostname}}
  • +
  • OS: {{.OS}} / {{.Arch}}
  • +
  • Go: {{.GoVersion}}
  • +
+
+ +
+

🌐 Network

+
    + {{range .IPs}}
  • IP: {{.}}
  • {{end}} + {{range .MACs}}
  • MAC: {{.}}
  • {{end}} +
+
+ +
+

⏱ Runtime

+
    +
  • Started: {{.StartTime}}
  • +
  • Now: {{.Now}}
  • +
+
+ +
+

🌱 Environment Variables

+
    + {{range $k, $v := .Env}} +
  • {{$k}} = {{$v}}
  • + {{end}} +
+
+
+ + + diff --git a/web/templates/index_to_update.html b/web/templates/index_to_update.html new file mode 100644 index 0000000..db9886c --- /dev/null +++ b/web/templates/index_to_update.html @@ -0,0 +1,215 @@ + + + + +Host Info + + + + + + +
+ +
+

🚀 Host Info Dashboard

+

Runtime, container, cloud and Kubernetes metadata

+
+ +
+ + +
+

🖥 System

+
    +
  • Hostname{{.Hostname}}
  • +
  • OS{{.OS}} / {{.Arch}}
  • +
  • Go{{.GoVersion}}
  • +
  • Started{{.StartTime}}
  • +
  • Now{{.Now}}
  • +
+
+ + +
+

🌐 Network

+
    + {{range .IPs}} +
  • IP{{.}}
  • + {{end}} + {{range .MACs}} +
  • MAC{{.}}
  • + {{end}} +
+
+ + +
+

☁️ Cloud

+
    +
  • + Provider + + {{if .Cloud.Provider}} + {{.Cloud.Provider}} + {{else}} + local + {{end}} + +
  • + {{if .Cloud.Instance}}
  • Instance{{.Cloud.Instance}}
  • {{end}} + {{if .Cloud.Region}}
  • Region{{.Cloud.Region}}
  • {{end}} + {{if .Cloud.Zone}}
  • Zone{{.Cloud.Zone}}
  • {{end}} + {{range $k, $v := .Cloud.Extra}} +
  • {{$k}}{{$v}}
  • + {{end}} +
+
+ + + {{if .Kubernetes.Enabled}} +
+

☸️ Kubernetes

+
    +
  • Pod{{.Kubernetes.PodName}}
  • +
  • Namespace{{.Kubernetes.PodNamespace}}
  • +
  • Pod IP{{.Kubernetes.PodIP}}
  • +
  • Node{{.Kubernetes.NodeName}}
  • +
  • ServiceAccount{{.Kubernetes.ServiceAccount}}
  • +
  • Container{{.Kubernetes.Container}}
  • +
+
+ {{end}} + + +
+

🌱 Environment

+
    + {{range $k, $v := .Env}} +
  • {{$k}}{{$v}}
  • + {{end}} +
+
+ +
+ + + +
+ +