From 6b1f93f380b85af324d38ab273c10ecf72d78770 Mon Sep 17 00:00:00 2001
From: MaksymLeus
Date: Thu, 22 Jan 2026 20:06:44 +0200
Subject: [PATCH 1/2] feat: Initial commit
---
.dockerignore | 3 +
.github/workflows/ci.yml | 121 ++++++++
.github/workflows/template-docker.yml | 221 +++++++++++++
.../workflows/template-semantic-release.yml | 89 ++++++
.gitignore | 33 ++
.releaserc.json | 17 +
CHANGELOG.md | 6 +
LICENSE.md | 21 ++
README copy.md | 240 ++++++++++++++
cmd/server/hostinfo.go | 263 ++++++++++++++++
docker/Dockerfile | 98 ++++++
docker/docker-compose.yml | 21 ++
docs/00-overview.md | 167 ++++++++++
docs/01-getting-started.md | 145 +++++++++
docs/02-installation.md | 213 +++++++++++++
docs/03-usage.md | 158 ++++++++++
docs/05-architecture.md | 239 ++++++++++++++
docs/06-deployment.md | 293 ++++++++++++++++++
docs/07-cloud-detection.md | 178 +++++++++++
docs/08-api.md | 206 ++++++++++++
docs/09-releasing.md | 174 +++++++++++
docs/TODO.md | 60 ++++
docs/semantic.md | 128 ++++++++
docs/test.md | 0
go.mod | 3 +
.../favicon_io/android-chrome-192x192.png | Bin 0 -> 3108 bytes
.../favicon_io/android-chrome-512x512.png | Bin 0 -> 9026 bytes
internal/favicon_io/apple-touch-icon.png | Bin 0 -> 2917 bytes
internal/favicon_io/favicon-16x16.png | Bin 0 -> 542 bytes
internal/favicon_io/favicon-32x32.png | Bin 0 -> 806 bytes
internal/favicon_io/favicon.ico | Bin 0 -> 15406 bytes
new-readme.md | 183 +++++++++++
scripts/hooks/commit-msg | 24 ++
scripts/hooks/pre-commit | 28 ++
tools/build.sh | 60 ++++
tools/setup-hooks.sh | 6 +
web/image.png | Bin 0 -> 205603 bytes
web/templates/index.html | 95 ++++++
web/templates/index_to_update.html | 215 +++++++++++++
39 files changed, 3708 insertions(+)
create mode 100644 .dockerignore
create mode 100644 .github/workflows/ci.yml
create mode 100644 .github/workflows/template-docker.yml
create mode 100644 .github/workflows/template-semantic-release.yml
create mode 100644 .gitignore
create mode 100644 .releaserc.json
create mode 100644 CHANGELOG.md
create mode 100644 LICENSE.md
create mode 100644 README copy.md
create mode 100644 cmd/server/hostinfo.go
create mode 100644 docker/Dockerfile
create mode 100644 docker/docker-compose.yml
create mode 100644 docs/00-overview.md
create mode 100644 docs/01-getting-started.md
create mode 100644 docs/02-installation.md
create mode 100644 docs/03-usage.md
create mode 100644 docs/05-architecture.md
create mode 100644 docs/06-deployment.md
create mode 100644 docs/07-cloud-detection.md
create mode 100644 docs/08-api.md
create mode 100644 docs/09-releasing.md
create mode 100644 docs/TODO.md
create mode 100644 docs/semantic.md
create mode 100644 docs/test.md
create mode 100644 go.mod
create mode 100644 internal/favicon_io/android-chrome-192x192.png
create mode 100644 internal/favicon_io/android-chrome-512x512.png
create mode 100644 internal/favicon_io/apple-touch-icon.png
create mode 100644 internal/favicon_io/favicon-16x16.png
create mode 100644 internal/favicon_io/favicon-32x32.png
create mode 100644 internal/favicon_io/favicon.ico
create mode 100644 new-readme.md
create mode 100755 scripts/hooks/commit-msg
create mode 100755 scripts/hooks/pre-commit
create mode 100644 tools/build.sh
create mode 100755 tools/setup-hooks.sh
create mode 100644 web/image.png
create mode 100644 web/templates/index.html
create mode 100644 web/templates/index_to_update.html
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..f33e95f
--- /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
+[](https://golang.org)
+[](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)
+
+
+## 📦 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 0000000000000000000000000000000000000000..1e59e91fed3b1c9e6c7b909120c98faf39dd25df
GIT binary patch
literal 3108
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
H?lJ!XrXf?S
literal 0
HcmV?d00001
diff --git a/internal/favicon_io/android-chrome-512x512.png b/internal/favicon_io/android-chrome-512x512.png
new file mode 100644
index 0000000000000000000000000000000000000000..06f6d974cb01ab435dd3362be28f3eb4cb917136
GIT binary patch
literal 9026
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
literal 0
HcmV?d00001
diff --git a/internal/favicon_io/apple-touch-icon.png b/internal/favicon_io/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..006a1d9931e188acd30b4a235ca35d84b565eff3
GIT binary patch
literal 2917
zcmai0XH-+!7QPfAfItWeNYNOP&`|?JR}x`pLr_EzL_nHI69h$iF(?TNIw*urs5(+6
zLuV*H3F1&gk@n~{bTmjXw0B(V{djM!H|zbl_uPH&J^P&f?eE+B5U=2k1^FfUAqWyQ
zH8HdTW95$v#slv9T|5^cNI=rm5NrL2V>QdtN7`1VEvi`)^XE#zaSCykmb>>L*#+Ku
zANPSz;B71l244=WqdV47J`!&z$c`Dzd=wET4u)%q
zH4}-Zzb1*c7R*!
z)-|%7QmIi>K8`X^(yj=$zxSnXM-t`o+xPr!Tu)EtsY&YYh(KxXmxrU0Ik2+u#e{NS
zwb4}VGt3>s*>9B-Gl2n7lRE5K%WGYys>F4yNAGm;c5YNJ4HSARhHt)ds#LSQhb~ML
zD%$?2?(2xkm@&{Kt@l*zd#P1=$K>Yp!VDO_JJWt`FaysvPONS|h#>z<%XlIf6OY6`
z{P_&sn9)m`^@Gc&Lj=`yt?iE25TX0H8cEy&!iif*@6MY+xJ8n9Yl0j^NswD#jy%33
zfU}h~c}B%UxbndFED}7Lew+{CS|S=vUyTDB-ouY*q`
zE&GGP&HmdXOH0)q8&9T^jU(^pPD(d+hc#U^DahEr&`*n*t@nSajFw`^3DX##g5bxij#D!AJqy(QAN$ML{n)jvD~0*qsW@=16{9AhkeR(mEsF8DmSRtfMp5T9IG;NR;Tzrcpia8#y|u-
z^tfeS7NT^dN!8<#5JmL;l)pBg0ptF{lQL0Qt*UyC)r0h
z?cKEZ_)?dem=ngLy{vfZyOsHlZp!t2j+YiHe1607W@kexxn#@ak)CO^>G^AiWAUs}
zcBkPd+#_ou(3@2{IS#2V`{o!2?ABYY^G69!xDec#Z#gJgM9$|XVB?5mS6fbS!bj8O
z>G9kgFcqwbM&@bgKR)++O*v2i5h=A)y9w?RSn2Rki11)VQ&GJ7_-Fgcmp6(ANh4@=
z^M#IY2W~~3RixvTz%2Pvt^WM7L`Ok|P^`;cnI@mea|g-Q^ZtuLX#V^S^DVIqS#F-(F-_agu;KvVPRhiK8@d5_!U*T6g{{8OQje>c0LZx`BwDaP!gOx7jov4i5k`aVs
z<;A)do`oppQZkYo|F&BZ@G(K0zz0KxT>#YnQF;s#AOjOETm`je8)Lv5%guve_tcCE
zpVHrIs;_R}J!>=kys1PZLSC!wec@&w>;0PxPM&{6z~k87h%%anjctD2C5Mf%Qw5Y5
zLZZO_I$QWs?bmMa8d;lTOS&}s^$o=~L7`fQs6zjKi8K1omG@uUGQSqAhf>ID&4+DE
zx`u0>a0xB}eUtMJT%6Sa)=O-fs4uL~kDr8&}_@cy&iD6}uu5LVT4Y4;^fF8rPXxo~D3do3H$+9?Z!PjSt&d
z1pg+Wop#e!73m|GG~j%G-!BO`lq>7nSgcgy|nu
zaahp?oo)P_eqldcjM{22
zY;GBNG_dqb+NmNIM)0Q{3)Z?yxlC-n8nx`t3QDgQD<
zvB-Wl4H@*jrF@0~uP417d?W`O$An8OD{&~{dion!-}2XPp*=>E5i6LSLz?VewuyHH
zy4)z;HYiXbooxS*J)q6rn-f#3
zrF-(+Gb>pb9!9M&cuyOt%~cH!en`DJG_fDt+n}ntzj}Lkts#BLAz=KCXk(xCp41Od
ziu8Lvmlj&1_KH0n+>AUEgC3KG&NC}%7pk%a6lx!QUru9vwYM$Hstb6jDs~_>DnSuH
zoM=k>;pKX%AN9oEO1Nw9JwMB*xG@Y
zhxowADIu+j2F0>_jQ)TVbYh6-X$R0@Dy9ZiuR4DcUjK?pS&{|JBOXmm{S_R2+FVmGCT=1_$U&v6ZB7-~k0g4+kmY34*5HR{G~U8X
zWdRSS-_KOVxl2LseqWqtS&nOlNQZY?O>wQh+nWA%{gXK66*>C#B>n6{+@K7|yo@kx
zP0$#^3AYK@>f)yk!5R7R@Pa~s0(jv0aZ2Wb;IR>#XBK#B!5hfb2ya-1bB_HNWLahw
literal 0
HcmV?d00001
diff --git a/internal/favicon_io/favicon-16x16.png b/internal/favicon_io/favicon-16x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..14eb75b34a1895af9bf4b912b2211cb077361b34
GIT binary patch
literal 542
zcmV+(0^$9MP)>JEwms}fRw_q-GD+#8Vo0X0}u^PkSwt2jG!`q5QLM8{6MO6iWEr^
zDftAai_#=gZZdD3U0)AIlYduMHzLNM~?mvoi?t?a5s8?F_AQ2G;-p0RR8BIh)J?
g000I_L_t&o0H}S$YZqb*9RL6T07*qoM6N<$g39all>h($
literal 0
HcmV?d00001
diff --git a/internal/favicon_io/favicon-32x32.png b/internal/favicon_io/favicon-32x32.png
new file mode 100644
index 0000000000000000000000000000000000000000..3537defc2718ed2a3a5d00049edb921a08294dd6
GIT binary patch
literal 806
zcmV+>1KIqEP)rHN@+5Lp?5FmrvU&9tvGuY;9hSupA8y!Xzz_nmq3K1NZSuciznujG4@L%GEG
zVCF*5cX~M64Gx+h7gcY{fa>I1j#5jGb7mxz)ax7p$1$1+aS;to8HmX9&s6&jq!NV}
zHxv0>qenkwvQky`(r8B~0rJAEzJt5Rq?&=DXjH
zy9litTx8K|ATpxGWcv;5Dkm-;XZ4!@;e&|B=nxlM=8&FMV}5?z#iOU_TfO=4q~z^;
zTg0s6Dqtf0qt;%?$jroChRg02`YykMmuoYh7M7;tKJ+AfdzjvJJO|^2=$)S*_t(p_
zYtWPppkRV6Lr5g?RHBgdp_iR;&!0;Y&q%^6c*M
zg8C9YXkuy*>T4K11WDz6C_yN!j7Zc>Dl!PQKlbnU?e6TEowIw+p1pUu$=w5UXLsiN
zH{X2UH{Z5iTd*MLpQCBR(==@^V1NP)
zza3TEpQBsz8V1{D4^;0<0;E2IAy^g0)j4xb{;y+L8`VDXPJIMpn~aM#QP)g9)z+(q
zqcayU!+^C#pKGu>wYj?0AQ*xbeQNC*d^_=QO?F810so-ahCb1z)?)mCJr=;8q_KMA
zHThRT+p$jL_jwclR!+)BIr!
zABZs|)_@WAE8~-n9HY9ZEe(SmAsB+CAK^RY>I(nO|GTAJnGYH-lKgg=aVnUbVQQm3
z8CJy=ePrXR{ZgCg?^I)6C(1rT&(zilFH1kgnO>BoC={Q%`8DoYF=c4+;B+dEK6q<4
zxH2r^3ObD?D-aRW{oq@ETp(XH*x7Vgtx%pFh5p`*JFSq>mT}J?zC&vQ60*
zqWTMI&-e2voC{n*$0_rOKSnj4{AQbch8i;(G3%5ZM+N+lUY(gtIbF|qt6118M-z-w5hnFc=sUqXIk
z@^fJ^_~lOWx8onDKhPtZArJCvl7alQp|j;!E58#3I=nRM{4pj$->b{k1H?Pv)3yHE
zjX#b43;bmJjkO=j{pCEswB#pPdV+s+E%8vaVFR7P*V5;?XCk&^(coZvpjj^C`R_57pJb3_x>(0NgmMS@
z2K3`%uAYh%oxMEP_5-_v4J&e$Ej!GUf_som^P9K*6!c@x-((-o*?$ZDx79z+c7LH*
zpq}U0IzOz&EZiTxufhRlEn>@g6}I`F_x#GXl*e-+|Nk+6j>}Kt;(u*`--dOI{3_@;EcOkz((VQ}S96I?CI-zffMtvR-%dQY@@VJ1p=qEzPF%Mbf$GP_$_`+&@
zySM?oJ{)a3g6tpR?*AEl8@s07&s*|ic^Fyb7x-<@4@dG%iNRB!pGV|3_$k=+DU5%I
zkg;7|>+73{Z$BD@(=5SvY5soUY_na4!L!LvqfhL2f0N
+
+
+
+
+
+
+
+
+**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 0000000000000000000000000000000000000000..06e706d2b64e2a6a97a2dd3d0f29e7ee5bc69054
GIT binary patch
literal 205603
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