Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87378e49ac | |||
| 100434fc3c | |||
| deef4702f4 | |||
| 1c2415bc1b | |||
| fac09b381f |
@@ -0,0 +1,19 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cargo fmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-targets --all-features -D warnings
|
||||
- name: Build
|
||||
run: cargo build --release --workspace
|
||||
@@ -0,0 +1 @@
|
||||
/target
|
||||
@@ -1,423 +0,0 @@
|
||||
# APT Repository Setup Summary
|
||||
|
||||
## 🎉 What You Now Have
|
||||
|
||||
You now have a complete system for creating and hosting your own APT repository for socktop packages, **without needing a sponsor or official Debian/Ubuntu approval**.
|
||||
|
||||
## 📁 Files Created
|
||||
|
||||
### Scripts (in `scripts/`)
|
||||
- **`init-apt-repo.sh`** - Initializes the APT repository directory structure
|
||||
- **`add-package-to-repo.sh`** - Adds .deb packages to the repository and generates metadata
|
||||
- **`sign-apt-repo.sh`** - Signs the repository with your GPG key
|
||||
- **`setup-apt-repo.sh`** - All-in-one interactive wizard to set everything up
|
||||
|
||||
### Documentation
|
||||
- **`QUICK_START_APT_REPO.md`** - Quick start guide (< 10 minutes)
|
||||
- **`docs/APT_REPOSITORY.md`** - Comprehensive 600+ line guide covering everything
|
||||
- **`APT_REPO_SUMMARY.md`** - This file
|
||||
|
||||
### GitHub Actions
|
||||
- **`.github/workflows/publish-apt-repo.yml`** - Automated building, signing, and publishing
|
||||
|
||||
## 🚀 Quick Start (Choose One)
|
||||
|
||||
### Option 1: Interactive Setup (Recommended for First Time)
|
||||
|
||||
Run the setup wizard:
|
||||
|
||||
```bash
|
||||
./scripts/setup-apt-repo.sh
|
||||
```
|
||||
|
||||
This walks you through:
|
||||
1. ✅ Checking prerequisites
|
||||
2. 🔑 Setting up GPG key
|
||||
3. 📦 Finding/building packages
|
||||
4. 📝 Creating repository structure
|
||||
5. ✍️ Signing the repository
|
||||
6. 📋 Next steps to publish to gh-pages
|
||||
|
||||
### Option 2: Manual Step-by-Step
|
||||
|
||||
```bash
|
||||
# 1. Initialize
|
||||
./scripts/init-apt-repo.sh
|
||||
|
||||
# 2. Build packages
|
||||
cargo deb --package socktop
|
||||
cargo deb --package socktop_agent
|
||||
|
||||
# 3. Add packages
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop_*.deb
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop-agent_*.deb
|
||||
|
||||
# 4. Sign (replace YOUR-KEY-ID)
|
||||
./scripts/sign-apt-repo.sh apt-repo stable YOUR-KEY-ID
|
||||
|
||||
# 5. Update URLs
|
||||
sed -i 's/YOUR-USERNAME/your-github-username/g' apt-repo/*.{md,html}
|
||||
|
||||
# 6. Publish to gh-pages (see below)
|
||||
```
|
||||
|
||||
### Option 3: Fully Automated (After Initial Setup)
|
||||
|
||||
Once gh-pages branch exists, just tag releases:
|
||||
|
||||
```bash
|
||||
git tag v1.50.0
|
||||
git push --tags
|
||||
|
||||
# GitHub Actions will:
|
||||
# - Build packages for AMD64 and ARM64
|
||||
# - Update APT repository
|
||||
# - Sign with your GPG key
|
||||
# - Push to gh-pages branch automatically
|
||||
```
|
||||
|
||||
## 📤 Publishing to GitHub Pages (gh-pages branch)
|
||||
|
||||
**Why gh-pages branch?**
|
||||
- ✅ Keeps main branch clean (source code only)
|
||||
- ✅ Separate branch for published content
|
||||
- ✅ GitHub Actions can auto-update it
|
||||
- ✅ You can customize the landing page
|
||||
|
||||
**Initial Setup:**
|
||||
```bash
|
||||
# Create gh-pages branch
|
||||
git checkout --orphan gh-pages
|
||||
git rm -rf .
|
||||
|
||||
# Copy apt-repo CONTENTS to root (not the folder!)
|
||||
cp -r apt-repo/* .
|
||||
rm -rf apt-repo
|
||||
|
||||
# Commit and push
|
||||
git add .
|
||||
git commit -m "Initialize APT repository"
|
||||
git push -u origin gh-pages
|
||||
|
||||
# Return to main
|
||||
git checkout main
|
||||
```
|
||||
|
||||
**Enable in GitHub:**
|
||||
1. Settings → Pages
|
||||
2. Source: **gh-pages** → **/ (root)**
|
||||
3. Save
|
||||
|
||||
Your repo will be at: `https://your-username.github.io/socktop/`
|
||||
|
||||
**Note:** GitHub Pages only allows `/` (root) or `/docs`. Since we use gh-pages branch, contents go in the root of that branch.
|
||||
|
||||
See `SETUP_GITHUB_PAGES.md` for detailed step-by-step instructions.
|
||||
|
||||
### Alternative: Self-Hosted Server
|
||||
|
||||
Copy `apt-repo/` contents to your web server:
|
||||
```bash
|
||||
rsync -avz apt-repo/ user@example.com:/var/www/apt/
|
||||
```
|
||||
|
||||
Configure Apache/Nginx to serve the directory. See `docs/APT_REPOSITORY.md` for details.
|
||||
|
||||
## 🤖 GitHub Actions Automation
|
||||
|
||||
### Required Secrets
|
||||
|
||||
Add these in GitHub Settings → Secrets → Actions:
|
||||
|
||||
1. **GPG_PRIVATE_KEY**
|
||||
```bash
|
||||
gpg --armor --export-secret-key YOUR-KEY-ID
|
||||
# Copy entire output including BEGIN/END lines
|
||||
```
|
||||
|
||||
2. **GPG_KEY_ID**
|
||||
```bash
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
# Use the ID after "rsa4096/"
|
||||
```
|
||||
|
||||
3. **GPG_PASSPHRASE**
|
||||
```bash
|
||||
# Your GPG passphrase (leave empty if no passphrase)
|
||||
```
|
||||
|
||||
### Triggers
|
||||
|
||||
The workflow runs on:
|
||||
- **Version tags**: `git tag v1.50.0 && git push --tags`
|
||||
- **Manual dispatch**: Actions tab → "Publish APT Repository" → Run workflow
|
||||
|
||||
### What It Does
|
||||
|
||||
1. ✅ Builds packages for AMD64 and ARM64
|
||||
2. ✅ Initializes or updates APT repository
|
||||
3. ✅ Generates Packages files and metadata
|
||||
4. ✅ Signs with your GPG key
|
||||
5. ✅ Commits and pushes to gh-pages branch
|
||||
6. ✅ Creates GitHub Release with artifacts
|
||||
7. ✅ Generates summary with installation instructions
|
||||
|
||||
## 👥 User Installation
|
||||
|
||||
Once published, users install with:
|
||||
|
||||
```bash
|
||||
# Add repository
|
||||
curl -fsSL https://your-username.github.io/socktop/KEY.gpg | \
|
||||
sudo gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg
|
||||
|
||||
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://your-username.github.io/socktop stable main" | \
|
||||
sudo tee /etc/apt/sources.list.d/socktop.list
|
||||
|
||||
# Install
|
||||
sudo apt update
|
||||
sudo apt install socktop socktop-agent
|
||||
|
||||
# The agent service is automatically installed and configured
|
||||
sudo systemctl enable --now socktop-agent
|
||||
```
|
||||
|
||||
## 🔧 Maintenance
|
||||
|
||||
### Release New Version (Automated)
|
||||
|
||||
```bash
|
||||
# Update version in Cargo.toml, commit changes
|
||||
git add . && git commit -m "Bump version to 1.51.0"
|
||||
git tag v1.51.0
|
||||
git push origin main --tags
|
||||
|
||||
# GitHub Actions automatically:
|
||||
# - Builds packages for AMD64 and ARM64
|
||||
# - Updates apt-repo
|
||||
# - Signs with GPG
|
||||
# - Pushes to gh-pages branch
|
||||
```
|
||||
|
||||
### Manual Update (if needed)
|
||||
|
||||
```bash
|
||||
# On main branch
|
||||
cargo deb --package socktop
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop_*.deb
|
||||
./scripts/sign-apt-repo.sh
|
||||
|
||||
# Switch to gh-pages and update
|
||||
git checkout gh-pages
|
||||
cp -r apt-repo/* .
|
||||
git add . && git commit -m "Release v1.51.0" && git push
|
||||
git checkout main
|
||||
```
|
||||
|
||||
### Remove Old Versions
|
||||
|
||||
```bash
|
||||
# On gh-pages branch
|
||||
git checkout gh-pages
|
||||
rm pool/main/socktop_1.50.0_*.deb
|
||||
# Regenerate metadata (re-add remaining packages)
|
||||
git add . && git commit -m "Remove old versions" && git push
|
||||
git checkout main
|
||||
```
|
||||
|
||||
## 🎯 Key Benefits
|
||||
|
||||
✅ **No sponsor needed** - Host your own repository
|
||||
✅ **Full control** - You decide when to release
|
||||
✅ **Free hosting** - GitHub Pages at no cost
|
||||
✅ **Automated** - GitHub Actions does the work
|
||||
✅ **Professional** - Just like official repos
|
||||
✅ **Multi-arch** - AMD64, ARM64 support built-in
|
||||
✅ **Secure** - GPG signed packages
|
||||
✅ **Easy updates** - Users get updates via `apt upgrade`
|
||||
|
||||
## 📊 Repository Structure
|
||||
|
||||
```
|
||||
apt-repo/
|
||||
├── dists/
|
||||
│ └── stable/
|
||||
│ ├── Release # Main metadata (checksums)
|
||||
│ ├── Release.gpg # Detached signature
|
||||
│ ├── InRelease # Clearsigned release
|
||||
│ └── main/
|
||||
│ ├── binary-amd64/
|
||||
│ │ ├── Packages # Package list
|
||||
│ │ ├── Packages.gz # Compressed
|
||||
│ │ └── Release # Component metadata
|
||||
│ ├── binary-arm64/
|
||||
│ └── binary-armhf/
|
||||
├── pool/
|
||||
│ └── main/
|
||||
│ ├── socktop_1.50.0_amd64.deb
|
||||
│ ├── socktop-agent_1.50.1_amd64.deb
|
||||
│ ├── socktop_1.50.0_arm64.deb
|
||||
│ └── socktop-agent_1.50.1_arm64.deb
|
||||
├── KEY.gpg # Public GPG key
|
||||
├── README.md # Repository info
|
||||
├── index.html # Web interface
|
||||
└── packages.html # Package listing
|
||||
```
|
||||
|
||||
## 🔑 GPG Key Management
|
||||
|
||||
### Create New Key
|
||||
|
||||
```bash
|
||||
gpg --full-generate-key
|
||||
# Choose RSA 4096, no expiration (or 2 years)
|
||||
```
|
||||
|
||||
### Export Keys
|
||||
|
||||
```bash
|
||||
# Public key (for users)
|
||||
gpg --armor --export YOUR-KEY-ID > KEY.gpg
|
||||
|
||||
# Private key (for GitHub Secrets)
|
||||
gpg --armor --export-secret-key YOUR-KEY-ID
|
||||
```
|
||||
|
||||
### Backup Keys
|
||||
|
||||
```bash
|
||||
# Backup to safe location
|
||||
gpg --export-secret-keys YOUR-KEY-ID > gpg-private-backup.key
|
||||
gpg --export YOUR-KEY-ID > gpg-public-backup.key
|
||||
```
|
||||
|
||||
### Key Rotation
|
||||
|
||||
If your key expires or is compromised:
|
||||
```bash
|
||||
./scripts/sign-apt-repo.sh apt-repo stable NEW-KEY-ID
|
||||
gpg --armor --export NEW-KEY-ID > apt-repo/KEY.gpg
|
||||
# Users need to re-import the key
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### "Repository not signed"
|
||||
```bash
|
||||
./scripts/sign-apt-repo.sh apt-repo stable YOUR-KEY-ID
|
||||
ls apt-repo/dists/stable/Release* # Should show 3 files
|
||||
```
|
||||
|
||||
### "Package not found"
|
||||
```bash
|
||||
cd apt-repo
|
||||
dpkg-scanpackages --arch amd64 pool/main /dev/null > dists/stable/main/binary-amd64/Packages
|
||||
gzip -9 -k -f dists/stable/main/binary-amd64/Packages
|
||||
cd ..
|
||||
./scripts/sign-apt-repo.sh
|
||||
```
|
||||
|
||||
### "404 Not Found" on GitHub Pages
|
||||
- Wait 2-3 minutes after pushing
|
||||
- Check Settings → Pages is enabled
|
||||
- Verify source branch/directory
|
||||
|
||||
### GitHub Actions not signing
|
||||
- Check all 3 secrets are set correctly
|
||||
- GPG_PRIVATE_KEY must include BEGIN/END lines
|
||||
- Test signing locally first
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
| File | Purpose | Length |
|
||||
|------|---------|--------|
|
||||
| `QUICK_START_APT_REPO.md` | Get started in < 10 minutes | Quick |
|
||||
| `SETUP_GITHUB_PAGES.md` | Detailed gh-pages setup guide | Step-by-step |
|
||||
| `docs/APT_REPOSITORY.md` | Complete guide with all options | Comprehensive |
|
||||
| `docs/DEBIAN_PACKAGING.md` | How .deb packages are built | Technical |
|
||||
| `DEBIAN_PACKAGING_SUMMARY.md` | Overview of packaging work | Summary |
|
||||
| `APT_REPO_SUMMARY.md` | This file | Overview |
|
||||
|
||||
## 🎓 Learning Path
|
||||
|
||||
1. **Start here**: `QUICK_START_APT_REPO.md` (10 min)
|
||||
2. **Set up**: Run `./scripts/setup-apt-repo.sh` (15 min)
|
||||
3. **Publish**: Follow `SETUP_GITHUB_PAGES.md` (5 min)
|
||||
4. **Automate**: Set up GitHub Actions secrets (10 min)
|
||||
5. **Advanced**: Read `docs/APT_REPOSITORY.md` as needed
|
||||
|
||||
## 🚦 Next Steps
|
||||
|
||||
Choose your path:
|
||||
|
||||
### Just Getting Started?
|
||||
1. ✅ Read `QUICK_START_APT_REPO.md`
|
||||
2. ✅ Run `./scripts/setup-apt-repo.sh`
|
||||
3. ✅ Follow `SETUP_GITHUB_PAGES.md` to publish
|
||||
4. ✅ Test installation on a VM
|
||||
|
||||
### Want Automation?
|
||||
1. ✅ Generate/export GPG key
|
||||
2. ✅ Add GitHub Secrets
|
||||
3. ✅ Tag a release: `git tag v1.50.0 && git push --tags`
|
||||
4. ✅ Watch GitHub Actions magic happen
|
||||
|
||||
### Want to Understand Everything?
|
||||
1. ✅ Read `docs/APT_REPOSITORY.md` (comprehensive)
|
||||
2. ✅ Study the scripts in `scripts/`
|
||||
3. ✅ Examine `.github/workflows/publish-apt-repo.yml`
|
||||
4. ✅ Learn about Debian repository format
|
||||
|
||||
### Ready for Production?
|
||||
1. ✅ Set up monitoring/analytics
|
||||
2. ✅ Create PPA for Ubuntu (Launchpad)
|
||||
3. ✅ Apply to Debian mentors for official inclusion
|
||||
4. ✅ Set up repository mirrors
|
||||
5. ✅ Document best practices for users
|
||||
|
||||
## 🌟 Success Criteria
|
||||
|
||||
You'll know you're successful when:
|
||||
|
||||
- [ ] Users can `apt install socktop`
|
||||
- [ ] Updates work with `apt upgrade`
|
||||
- [ ] Multiple architectures supported
|
||||
- [ ] Repository is GPG signed
|
||||
- [ ] GitHub Actions publishes automatically
|
||||
- [ ] Installation instructions in README
|
||||
- [ ] Zero sponsor or approval needed
|
||||
|
||||
## 💡 Pro Tips
|
||||
|
||||
1. **Test first**: Always test on a fresh VM before publishing
|
||||
2. **Keep versions**: Don't delete old .deb files immediately
|
||||
3. **Backup GPG key**: Store it safely offline
|
||||
4. **Monitor downloads**: Use GitHub Insights or server logs
|
||||
5. **Document everything**: Help users troubleshoot
|
||||
6. **Version consistently**: Use semantic versioning
|
||||
7. **Sign always**: Never publish unsigned repositories
|
||||
|
||||
## 🔗 Resources
|
||||
|
||||
- [Debian Repository Format](https://wiki.debian.org/DebianRepository/Format)
|
||||
- [GitHub Pages Docs](https://docs.github.com/en/pages)
|
||||
- [cargo-deb](https://github.com/kornelski/cargo-deb)
|
||||
- [Ubuntu PPA Guide](https://help.launchpad.net/Packaging/PPA)
|
||||
- [Debian Mentors](https://mentors.debian.net/)
|
||||
|
||||
## 🎊 Congratulations!
|
||||
|
||||
You now have everything you need to:
|
||||
- ✅ Create your own APT repository
|
||||
- ✅ Host it for free on GitHub Pages
|
||||
- ✅ Automate the entire process
|
||||
- ✅ Distribute packages professionally
|
||||
- ✅ Provide easy installation for users
|
||||
|
||||
**No sponsor required. No approval needed. You're in control!** 🚀
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Check the docs or open an issue.
|
||||
|
||||
**Ready to publish?** Run `./scripts/setup-apt-repo.sh` and follow the wizard!
|
||||
Generated
+2254
File diff suppressed because it is too large
Load Diff
+35
@@ -0,0 +1,35 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"socktop",
|
||||
"socktop_agent"
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
# async + streams
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
anyhow = "1.0"
|
||||
|
||||
# websocket
|
||||
tokio-tungstenite = "0.24"
|
||||
tungstenite = "0.24"
|
||||
url = "2.5"
|
||||
|
||||
# JSON + error handling
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
|
||||
# system stats
|
||||
sysinfo = "0.32"
|
||||
|
||||
# CLI UI
|
||||
ratatui = "0.28"
|
||||
crossterm = "0.27"
|
||||
|
||||
# date/time
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
# web server (remote-agent)
|
||||
axum = { version = "0.7", features = ["ws"] }
|
||||
@@ -1,109 +0,0 @@
|
||||
# 🚀 Get Your APT Repository Live in 5 Minutes
|
||||
|
||||
## You're Here Because...
|
||||
|
||||
You want to publish socktop packages via APT, but GitHub Pages won't let you select `apt-repo/` folder. Here's why and how to fix it:
|
||||
|
||||
**The Issue:** GitHub Pages only serves from `/` (root) or `/docs`, not custom folders like `/apt-repo`.
|
||||
|
||||
**The Solution:** Use a `gh-pages` branch where `apt-repo` contents go in the root.
|
||||
|
||||
## Quick Setup (5 Steps)
|
||||
|
||||
### 1. Create apt-repo locally (if you haven't)
|
||||
|
||||
```bash
|
||||
./scripts/setup-apt-repo.sh
|
||||
```
|
||||
|
||||
This creates `apt-repo/` with your packages and signs them.
|
||||
|
||||
### 2. Create gh-pages branch
|
||||
|
||||
```bash
|
||||
git checkout --orphan gh-pages
|
||||
git rm -rf .
|
||||
```
|
||||
|
||||
### 3. Copy apt-repo to root
|
||||
|
||||
```bash
|
||||
cp -r apt-repo/* .
|
||||
rm -rf apt-repo
|
||||
ls
|
||||
# You should see: dists/ pool/ KEY.gpg index.html README.md
|
||||
```
|
||||
|
||||
### 4. Push to GitHub
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Initialize APT repository"
|
||||
git push -u origin gh-pages
|
||||
git checkout main
|
||||
```
|
||||
|
||||
### 5. Enable GitHub Pages
|
||||
|
||||
1. Go to: **Settings → Pages**
|
||||
2. Source: **gh-pages** → **/ (root)**
|
||||
3. Click **Save**
|
||||
|
||||
**Done!** ✅ Your repo will be live at `https://your-username.github.io/socktop/` in 1-2 minutes.
|
||||
|
||||
## Test It
|
||||
|
||||
```bash
|
||||
curl -I https://your-username.github.io/socktop/KEY.gpg
|
||||
# Should return: HTTP/2 200
|
||||
```
|
||||
|
||||
## Install It (On Any Debian/Ubuntu System)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://your-username.github.io/socktop/KEY.gpg | \
|
||||
sudo gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg
|
||||
|
||||
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://your-username.github.io/socktop stable main" | \
|
||||
sudo tee /etc/apt/sources.list.d/socktop.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install socktop socktop-agent
|
||||
```
|
||||
|
||||
## What's Next?
|
||||
|
||||
### Now (Optional):
|
||||
- Customize `index.html` on gh-pages for a nice landing page
|
||||
- Add installation instructions to your main README
|
||||
|
||||
### Later:
|
||||
- Set up GitHub Actions automation (see `QUICK_START_APT_REPO.md`)
|
||||
- Add more architectures (ARM64, ARMv7)
|
||||
|
||||
## Understanding the Setup
|
||||
|
||||
```
|
||||
main branch: gh-pages branch:
|
||||
├── src/ ├── dists/
|
||||
├── Cargo.toml ├── pool/
|
||||
├── scripts/ ├── KEY.gpg
|
||||
└── apt-repo/ (local) └── index.html ← GitHub Pages serves this
|
||||
|
||||
Work here ↑ Published here ↑
|
||||
```
|
||||
|
||||
- **main**: Your development work
|
||||
- **gh-pages**: What users see/download
|
||||
- **apt-repo/**: Local folder (ignored in git, see `.gitignore`)
|
||||
|
||||
## Need More Help?
|
||||
|
||||
- **Quick start**: `QUICK_START_APT_REPO.md`
|
||||
- **Detailed setup**: `SETUP_GITHUB_PAGES.md`
|
||||
- **Why gh-pages?**: `WHY_GHPAGES_BRANCH.md`
|
||||
- **Full guide**: `docs/APT_REPOSITORY.md`
|
||||
|
||||
---
|
||||
|
||||
**You got this!** 🎉
|
||||
@@ -1,42 +0,0 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQGNBGkih7QBDADgX6sYMx2Lp6qcZxeCCizcy4TFsxcRJfp5mfbMplVES0hQToIP
|
||||
EMC11JqPwQdLliXKjUr8Z2kgM2oqvH+dkdgzUGrw6kTK8YHc+qs37iJAOVS9D72X
|
||||
tTld282NrtFwzb74nS2GKPkpWI7aSKBpHtWFPX/1ONsc56qGqFd3wwikEvCz8MeJ
|
||||
HwCD1JZ9F+2DyyXWsTJNgDwPloJSUbtyVuk2gd6PeTg7AQdx92Pk/mggmYbHtP8N
|
||||
wy072ku1g8K/hplmwIOGpSx1JWvAQkDU/Bb/jSqrYg2wSHO7IQnYE8I3x/zglYBl
|
||||
FYNh47TVQr0zPVSYR1MQkHU5YLBTDc5UgDvtcsYUiTtq4D/m8HWmKja0/UKGxvDJ
|
||||
P5sUPcp4dk77RdoCtUe5HImYGS8lo5N3+t0lz8sd9rYmRiIO4f7FJaJqJeHbUJyn
|
||||
iw/GCQh5D5/D571dICrEq/QhL+k5KhJljPGoVMGPFXJIc7q+CxvGp2oOo5fOlbOn
|
||||
3kSrM93AJPwT8FMAEQEAAbRFSmFzb24gV2l0dHkgKHNvY2t0b3AgYXB0IHNpZ25p
|
||||
bmcga2V5KSA8amFzb25wd2l0dHkrc29ja3RvcEBwcm90b24ubWU+iQHOBBMBCgA4
|
||||
FiEEHnVWqAU5uDlLwoINESwaeYRl+/IFAmkih7QCGwMFCwkIBwIGFQoJCAsCBBYC
|
||||
AwECHgECF4AACgkQESwaeYRl+/KV+gwAzfZVZEhO7MQV2EmNeKVK1GycFSm2oUAl
|
||||
ZbwNIEHu6+tOzqXJb8o65BtGlbLSGavsMpgRCK2SL83DdLOkutG1ahQiJr+5GaXC
|
||||
zbQgX+VWqGPZtQ+I6/rVoYZPMTCrqpAmFgvVpqv0xod7w8/wny8/XmhQ37KY2/0l
|
||||
B38oNTvdA7C8jzSrI6kr3XqurvQRW7z+MnC+nCp9Ob9bYtY0kpd4U3NrVdb8m32U
|
||||
d5LVFwD1OGvzLOSqyJ33IKjSJc4KLvW+aEsHXe+fHO9UEzH8Nbo5MmVvX3QIHiyq
|
||||
jD4zN16AGsGYqCK4irtQCiD3wBOdsG/RVkgIcdlmAH3EGEp7Ux8+7v1PXYI+UrSs
|
||||
XE7f1xFTJ2r5TMex6W3he073Em4qhQsrnMF5syTZsM6N+5UqXVOM1RuDVVXr7929
|
||||
hC3G8pK/A2W5Lwpxl2yzock2CxhvUn7M/xm4VbcPlWTCUd/QzU8VtsgaGHcuhi5e
|
||||
xHY1AU07STLB9RinjBVf2bmk4oDQcmB6uQGNBGkih7QBDACrjE+xSWP92n931/5t
|
||||
+tXcujwFlIpSZdbSQFr0B0YyjPRUP4FSzEGu8vuM5ChUfWKhmN1dDr5C4qFo9NgQ
|
||||
6oCN2HubajSGyXNwnOMlMb5ck79Ubmy9yDV9/ZLqpJJiozGap2/EnNoDhaANlmUg
|
||||
rfqUHpIB8XC2IZ0Itt05tp/u78dJiB+R6ReZn/bVUafNV4jIqYZfLRzI3FTJ4xvK
|
||||
FGs/ER+JajAdJQ8LPfazmDQSGw0huguxhopZwKQ/qWZMn1OHq/ZaPvCqbQt3irLw
|
||||
dLPDC4pEaYGRyADYeyuarG0DVyUQ9XRc/NufKDvOAn33LpBPBpcvNQAsVhWTCYl7
|
||||
ogQ+suVYVN8Tu7v4bUSHKwzXKvLN/ojJX/Fh7eTW4TPsgLHNHAEDUkSQozIe9vO6
|
||||
o+vydDqRxuXJgdkR7lqP6PQDYrhRYZGJf57eKf6VtTKYFaMbiMWPU+vcHeB0/iDe
|
||||
Pv81qro2LD2PG5WCzDpNETBceCTjykb9r0VHx4/JsiojKmsAEQEAAYkBtgQYAQoA
|
||||
IBYhBB51VqgFObg5S8KCDREsGnmEZfvyBQJpIoe0AhsMAAoJEBEsGnmEZfvyNp8M
|
||||
AIH+6+hGB3qADdnhNgb+3fN0511eK9Uk82lxgGARLcD8GN1UP0HlvEqkxCHy3PUe
|
||||
tHcsuYVz7i8pmpEGdFx9zv7MelenUsJniUQ++OZKx6iUG/MYqz//NxY+5lyRmcu2
|
||||
aYvUxhkgf9zgxXTkTyV2VV32mX//cHcwc+c/089QAPzCMaSrHdNK+ED9+k8uquJ1
|
||||
lSL9Bm15z/EV42v9Q/4KTM5OBLHpNw0Rvn9C0iuZVwHXBrrA/HSGXpA54AqNUMpZ
|
||||
kRPgLQcy5yVE2y1aXLXt2XdTn6YPzrAjNoazYYuCWHYIZU7dGkIswpsDirDLKHdD
|
||||
onb3VShmSpemYjsuFiqhfi6qwCkeHsz/CpQAp70SZ+z9oB8H80PJVKPbPIP3zEf3
|
||||
i7bcsqHA7stF+8sJclXgxBUBeDJ3O2jN/scBOcvNA6xoRp7+oJbnjDRuxBmh+fVg
|
||||
TIuw2++vTF2Ml0EMv7ePTpr7b1DofuJRNYGkuAIMVXHjLTqMiTJUce3OUy003zMg
|
||||
Dg==
|
||||
=AaPQ
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -1,221 +0,0 @@
|
||||
# Quick Start: Setting Up Your socktop APT Repository
|
||||
|
||||
This guide will get your APT repository up and running in **under 10 minutes**.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [ ] Debian packages built (or use GitHub Actions to build them)
|
||||
- [ ] GPG key for signing
|
||||
- [ ] GitHub repository with Pages enabled
|
||||
|
||||
## Step 1: Create GPG Key (if needed)
|
||||
|
||||
```bash
|
||||
# Generate a new key
|
||||
gpg --full-generate-key
|
||||
|
||||
# Select:
|
||||
# - RSA and RSA (default)
|
||||
# - 4096 bits
|
||||
# - Key does not expire (or 2 years)
|
||||
# - Your name and email
|
||||
|
||||
# Get your key ID
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
# Look for the part after "rsa4096/" - that's your KEY-ID
|
||||
```
|
||||
|
||||
## Step 2: Initialize Repository Locally
|
||||
|
||||
```bash
|
||||
cd socktop
|
||||
|
||||
# Create the repository structure
|
||||
./scripts/init-apt-repo.sh
|
||||
|
||||
# Build packages (or download from GitHub Actions)
|
||||
cargo install cargo-deb
|
||||
cargo deb --package socktop
|
||||
cargo deb --package socktop_agent
|
||||
|
||||
# Add packages to repository
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop_*.deb
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop-agent_*.deb
|
||||
|
||||
# Sign the repository (replace YOUR-KEY-ID with actual key ID)
|
||||
./scripts/sign-apt-repo.sh apt-repo stable YOUR-KEY-ID
|
||||
|
||||
# Update URLs with your GitHub username
|
||||
sed -i 's/YOUR-USERNAME/your-github-username/g' apt-repo/README.md apt-repo/index.html
|
||||
```
|
||||
|
||||
## Step 3: Publish to GitHub Pages (gh-pages branch)
|
||||
|
||||
```bash
|
||||
# Create gh-pages branch
|
||||
git checkout --orphan gh-pages
|
||||
git rm -rf .
|
||||
|
||||
# Copy apt-repo CONTENTS to root (not the folder itself)
|
||||
cp -r apt-repo/* .
|
||||
rm -rf apt-repo
|
||||
|
||||
# Commit and push
|
||||
git add .
|
||||
git commit -m "Initialize APT repository"
|
||||
git push -u origin gh-pages
|
||||
|
||||
# Go back to main branch
|
||||
git checkout main
|
||||
```
|
||||
|
||||
Then in GitHub:
|
||||
1. Go to **Settings → Pages**
|
||||
2. Source: **Deploy from a branch**
|
||||
3. Branch: **gh-pages** → **/ (root)** → **Save**
|
||||
|
||||
Wait 1-2 minutes, then visit: `https://your-username.github.io/socktop/`
|
||||
|
||||
## Step 4: Automate with GitHub Actions
|
||||
|
||||
Add these secrets to your repository (Settings → Secrets → Actions):
|
||||
|
||||
```bash
|
||||
# Export your private key
|
||||
gpg --armor --export-secret-key YOUR-KEY-ID
|
||||
|
||||
# Copy the ENTIRE output and save as secret: GPG_PRIVATE_KEY
|
||||
```
|
||||
|
||||
Add these three secrets:
|
||||
- **GPG_PRIVATE_KEY**: Your exported private key
|
||||
- **GPG_KEY_ID**: Your key ID (e.g., `ABC123DEF456`)
|
||||
- **GPG_PASSPHRASE**: Your key passphrase (leave empty if no passphrase)
|
||||
|
||||
The workflow in `.github/workflows/publish-apt-repo.yml` will now:
|
||||
- Build packages for AMD64 and ARM64
|
||||
- Update the APT repository
|
||||
- Sign with your GPG key
|
||||
- Push to gh-pages automatically
|
||||
|
||||
Trigger it by:
|
||||
- Creating a version tag: `git tag v1.50.0 && git push --tags`
|
||||
- Manual dispatch from GitHub Actions tab
|
||||
|
||||
## Step 5: Test It
|
||||
|
||||
On any Debian/Ubuntu system:
|
||||
|
||||
```bash
|
||||
# Add your repository
|
||||
curl -fsSL https://your-username.github.io/socktop/KEY.gpg | \
|
||||
sudo gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg
|
||||
|
||||
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://your-username.github.io/socktop stable main" | \
|
||||
sudo tee /etc/apt/sources.list.d/socktop.list
|
||||
|
||||
# Install
|
||||
sudo apt update
|
||||
sudo apt install socktop socktop-agent
|
||||
|
||||
# Verify
|
||||
socktop --version
|
||||
socktop_agent --version
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Add a New Version
|
||||
|
||||
```bash
|
||||
# Build new packages
|
||||
cargo deb --package socktop
|
||||
cargo deb --package socktop_agent
|
||||
|
||||
# Add to repository
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop_*.deb
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop-agent_*.deb
|
||||
|
||||
# Re-sign
|
||||
./scripts/sign-apt-repo.sh apt-repo stable YOUR-KEY-ID
|
||||
|
||||
# Publish
|
||||
cd docs/apt # or wherever your apt-repo is
|
||||
git add .
|
||||
git commit -m "Release v1.51.0"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### Or Just Tag and Let GitHub Actions Do It
|
||||
|
||||
```bash
|
||||
# Update version in Cargo.toml
|
||||
# Commit changes
|
||||
git add .
|
||||
git commit -m "Bump version to 1.51.0"
|
||||
|
||||
# Tag and push
|
||||
git tag v1.51.0
|
||||
git push origin main --tags
|
||||
|
||||
# GitHub Actions will:
|
||||
# - Build packages for AMD64 and ARM64
|
||||
# - Update gh-pages branch automatically
|
||||
# - Sign and publish!
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Repository not signed" error
|
||||
|
||||
Make sure you signed it:
|
||||
```bash
|
||||
./scripts/sign-apt-repo.sh apt-repo stable YOUR-KEY-ID
|
||||
ls apt-repo/dists/stable/Release*
|
||||
# Should show: Release, Release.gpg, InRelease, KEY.gpg
|
||||
```
|
||||
|
||||
### "404 Not Found" on GitHub Pages
|
||||
|
||||
1. Check Settings → Pages is enabled
|
||||
2. Wait 2-3 minutes for GitHub to deploy
|
||||
3. Verify the URL structure matches your settings
|
||||
|
||||
### GitHub Actions not signing
|
||||
|
||||
Check that all three secrets are set correctly:
|
||||
- Settings → Secrets and variables → Actions
|
||||
- Make sure GPG_PRIVATE_KEY includes the BEGIN/END lines
|
||||
- Test locally first
|
||||
|
||||
## What's Next?
|
||||
|
||||
✅ You now have a working APT repository!
|
||||
|
||||
**Share it:**
|
||||
- Add installation instructions to your main README
|
||||
- Tweet/blog about it
|
||||
- Submit to awesome-rust lists
|
||||
|
||||
**Improve it:**
|
||||
- Customize your GitHub Pages site (it's just HTML!)
|
||||
- Add more architectures (ARMv7)
|
||||
- Create multiple distributions (stable, testing)
|
||||
- Set up download statistics
|
||||
- Apply to Ubuntu PPA (Launchpad)
|
||||
- Eventually submit to official Debian repos
|
||||
|
||||
## Full Documentation
|
||||
|
||||
For detailed information, see:
|
||||
- `docs/APT_REPOSITORY.md` - Complete APT repository guide
|
||||
- `docs/DEBIAN_PACKAGING.md` - Debian packaging details
|
||||
- `DEBIAN_PACKAGING_SUMMARY.md` - Quick summary
|
||||
|
||||
## Questions?
|
||||
|
||||
Open an issue on GitHub or check the full documentation.
|
||||
|
||||
---
|
||||
|
||||
**Happy packaging! 📦**
|
||||
@@ -1,38 +1,248 @@
|
||||
# socktop APT Repository
|
||||
# socktop
|
||||
|
||||
This repository contains Debian packages for socktop and socktop-agent.
|
||||
**socktop** is a remote system monitor with a rich TUI interface, inspired by `top` and `btop`, that communicates with a lightweight remote agent over WebSockets.
|
||||
|
||||
## Adding this repository
|
||||
It lets you watch CPU, memory, disks, network, temperatures, and processes on another machine in real-time — from the comfort of your terminal.
|
||||
|
||||
Add the repository to your system:
|
||||

|
||||
|
||||
```bash
|
||||
# Add the GPG key
|
||||
curl -fsSL https://jasonwitty.github.io/socktop/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg
|
||||
---
|
||||
|
||||
# Add the repository
|
||||
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://jasonwitty.github.io/socktop stable main" | sudo tee /etc/apt/sources.list.d/socktop.list
|
||||
## Features
|
||||
|
||||
# Update and install
|
||||
sudo apt update
|
||||
sudo apt install socktop socktop-agent
|
||||
- 📡 **Remote monitoring** via WebSocket — lightweight agent sends JSON metrics
|
||||
- 🖥 **Rich TUI** built with [ratatui](https://github.com/ratatui-org/ratatui)
|
||||
- 🔍 **Detailed CPU view** — per-core history, current load, and trends
|
||||
- 📊 **Memory, Swap, Disk usage** — human-readable units, color-coded
|
||||
- 🌡 **Temperatures** — CPU temperature with visual indicators
|
||||
- 📈 **Network throughput** — live sparkline graphs with peak tracking
|
||||
- 🏷 **Top processes table** — PID, name, CPU%, memory, and memory%
|
||||
- 🎨 Color-coded load, zebra striping for readability
|
||||
- ⌨ **Keyboard shortcuts**:
|
||||
- `q` / `Esc` → Quit
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
`socktop` has **two components**:
|
||||
|
||||
1. **Agent** (remote side)
|
||||
A small Rust WebSocket server that runs on the target machine and gathers metrics via [sysinfo](https://crates.io/crates/sysinfo).
|
||||
|
||||
2. **Client** (local side)
|
||||
The TUI app (`socktop`) that connects to the agent’s `/ws` endpoint, receives JSON metrics, and renders them.
|
||||
|
||||
The two communicate over a persistent WebSocket connection.
|
||||
|
||||
---
|
||||
|
||||
## Adaptive (idle-aware) sampling
|
||||
|
||||
The socktop agent now samples system metrics only when at least one WebSocket client is connected. When idle (no clients), the sampler sleeps and CPU usage drops to ~0%.
|
||||
|
||||
How it works
|
||||
- The WebSocket handler increments/decrements a client counter in `AppState` on connect/disconnect.
|
||||
- A background sampler wakes when the counter transitions from 0 → >0 and sleeps when it returns to 0.
|
||||
- The most recent metrics snapshot is cached as JSON for fast responses.
|
||||
|
||||
Cold start behavior
|
||||
- If a client requests metrics while the cache is empty (e.g., just started or after a long idle), the agent performs a one-off synchronous collection to respond immediately.
|
||||
|
||||
Tuning
|
||||
- Sampling interval (active): update `spawn_sampler(state, Duration::from_millis(500))` in `socktop_agent/src/main.rs`.
|
||||
- Always-on or low-frequency idle sampling: replace the “sleep when idle” logic in `socktop_agent/src/sampler.rs` with a low-frequency interval. Example sketch:
|
||||
|
||||
```rust
|
||||
// In sampler.rs (sketch): sample every 10s when idle, 500ms when active
|
||||
let idle_period = Duration::from_secs(10);
|
||||
loop {
|
||||
let active = state.client_count.load(Ordering::Relaxed) > 0;
|
||||
let period = if active { Duration::from_millis(500) } else { idle_period };
|
||||
let mut ticker = tokio::time::interval(period);
|
||||
ticker.tick().await;
|
||||
if !active {
|
||||
// wake early if a client connects
|
||||
tokio::select! {
|
||||
_ = ticker.tick() => {},
|
||||
_ = state.wake_sampler.notified() => continue,
|
||||
}
|
||||
}
|
||||
let m = collect_metrics(&state).await;
|
||||
if let Ok(js) = serde_json::to_string(&m) {
|
||||
*state.last_json.write().await = js;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
---
|
||||
|
||||
You can also download and install packages manually from the `pool/main/` directory.
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
- Rust 1.75+ (recommended latest stable)
|
||||
- Cargo package manager
|
||||
|
||||
### Build from source
|
||||
```bash
|
||||
wget https://jasonwitty.github.io/socktop/pool/main/socktop_VERSION_ARCH.deb
|
||||
sudo dpkg -i socktop_VERSION_ARCH.deb
|
||||
git clone https://github.com/YOURNAME/socktop.git
|
||||
cd socktop
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Supported Architectures
|
||||
### Install as a cargo binary
|
||||
```bash
|
||||
cargo install --path .
|
||||
```
|
||||
This will install the `socktop` binary into `~/.cargo/bin`.
|
||||
|
||||
- amd64 (x86_64)
|
||||
- arm64 (aarch64)
|
||||
- armhf (32-bit ARM)
|
||||
---
|
||||
|
||||
## Building from Source
|
||||
## Running
|
||||
|
||||
See the main repository at https://github.com/jasonwitty/socktop
|
||||
### 1. Start the agent on the remote machine
|
||||
The agent binary listens on a TCP port and serves `/ws`:
|
||||
|
||||
```bash
|
||||
remote_agent 0.0.0.0:8080
|
||||
```
|
||||
|
||||
> **Tip:** You can run the agent under `systemd`, inside a Docker container, or just in a tmux/screen session.
|
||||
|
||||
### 2. Connect with the client
|
||||
From your local machine:
|
||||
```bash
|
||||
socktop ws://REMOTE_HOST:8080/ws
|
||||
```
|
||||
|
||||
Example:
|
||||
```bash
|
||||
socktop ws://192.168.1.50:8080/ws
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
When connected, `socktop` displays:
|
||||
|
||||
**Left column:**
|
||||
- **CPU avg graph** — sparkline of recent overall CPU usage
|
||||
- **Memory gauge** — total and used RAM
|
||||
- **Swap gauge** — total and used swap
|
||||
- **Disks** — usage per device (only devices with available space > 0)
|
||||
- **Network Download/Upload** — sparkline in KB/s, with current & peak values
|
||||
|
||||
**Right column:**
|
||||
- **Per-core history & trends** — each core’s recent load, current %, and trend arrow
|
||||
- **Top processes table** — top 20 processes with PID, name, CPU%, memory usage, and memory%
|
||||
|
||||
---
|
||||
|
||||
## Configuring the agent port
|
||||
|
||||
The agent listens on TCP port 3000 by default. You can override this via a CLI flag, a positional port argument, or an environment variable:
|
||||
|
||||
- CLI flag:
|
||||
- socktop_agent --port 8080
|
||||
- socktop_agent -p 8080
|
||||
- Positional:
|
||||
- socktop_agent 8080
|
||||
- Environment variable:
|
||||
- SOCKTOP_PORT=8080 socktop_agent
|
||||
|
||||
Help:
|
||||
- socktop_agent --help
|
||||
|
||||
The TUI should point to ws://HOST:PORT/ws, e.g.:
|
||||
- cargo run -p socktop -- ws://127.0.0.1:8080/ws
|
||||
|
||||
---
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Key | Action |
|
||||
|-------------|------------|
|
||||
| `q` or `Esc`| Quit |
|
||||
|
||||
---
|
||||
|
||||
## Security (optional token)
|
||||
By default, the agent exposes metrics over an unauthenticated WebSocket. For untrusted networks, set an auth token and pass it in the client URL:
|
||||
|
||||
- Server:
|
||||
- SOCKTOP_TOKEN=changeme socktop_agent --port 3000
|
||||
- Client:
|
||||
- socktop ws://HOST:3000/ws?token=changeme
|
||||
|
||||
---
|
||||
|
||||
## Platform notes
|
||||
- Linux x86_64/AMD/Intel: fully supported.
|
||||
- Raspberry Pi:
|
||||
- 64-bit: rustup target add aarch64-unknown-linux-gnu; build on-device for simplicity.
|
||||
- 32-bit: rustup target add armv7-unknown-linux-gnueabihf.
|
||||
- Windows:
|
||||
- TUI and agent build/run with stable Rust. Use PowerShell:
|
||||
- cargo run -p socktop_agent -- --port 3000
|
||||
- cargo run -p socktop -- ws://127.0.0.1:3000/ws
|
||||
- CPU temperature may be unavailable; display will show N/A.
|
||||
|
||||
---
|
||||
|
||||
## Example agent JSON
|
||||
`socktop` expects the agent to send metrics in this shape:
|
||||
```json
|
||||
{
|
||||
"cpu_total": 12.4,
|
||||
"cpu_per_core": [11.2, 15.7, ...],
|
||||
"mem_total": 33554432,
|
||||
"mem_used": 18321408,
|
||||
"swap_total": 0,
|
||||
"swap_used": 0,
|
||||
"process_count": 127,
|
||||
"hostname": "myserver",
|
||||
"cpu_temp_c": 42.5,
|
||||
"disks": [{"name":"nvme0n1p2","total":512000000000,"available":320000000000}],
|
||||
"networks": [{"name":"eth0","received":12345678,"transmitted":87654321}],
|
||||
"top_processes": [
|
||||
{"pid":1234,"name":"nginx","cpu_usage":1.2,"mem_bytes":12345678}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Run in debug mode:
|
||||
```bash
|
||||
cargo run -- ws://127.0.0.1:8080/ws
|
||||
```
|
||||
|
||||
### Code formatting & lint:
|
||||
```bash
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
- [ ] Configurable refresh interval
|
||||
- [ ] Filter/sort top processes in the TUI
|
||||
- [ ] Export metrics to file
|
||||
- [ ] TLS / WSS support
|
||||
- [ ] Agent authentication
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
MIT License — see [LICENSE](LICENSE).
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgements
|
||||
- [`ratatui`](https://github.com/ratatui-org/ratatui) for terminal UI rendering
|
||||
- [`sysinfo`](https://crates.io/crates/sysinfo) for system metrics
|
||||
- [`tokio-tungstenite`](https://crates.io/crates/tokio-tungstenite) for WebSocket client/server
|
||||
|
||||
@@ -1,351 +0,0 @@
|
||||
# Setting Up GitHub Pages for socktop APT Repository
|
||||
|
||||
This guide walks you through the initial setup of your APT repository on GitHub Pages using the `gh-pages` branch.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [ ] You've run `./scripts/setup-apt-repo.sh` or manually created `apt-repo/`
|
||||
- [ ] `apt-repo/` contains signed packages and metadata
|
||||
- [ ] You have a GitHub repository for socktop
|
||||
|
||||
## Step-by-Step Setup
|
||||
|
||||
### 1. Verify Your Local Repository
|
||||
|
||||
First, make sure everything is ready:
|
||||
|
||||
```bash
|
||||
# Check that apt-repo exists and has content
|
||||
ls -la apt-repo/
|
||||
|
||||
# You should see:
|
||||
# - dists/stable/Release, Release.gpg, InRelease
|
||||
# - pool/main/*.deb
|
||||
# - KEY.gpg
|
||||
# - index.html, README.md
|
||||
```
|
||||
|
||||
### 2. Create and Switch to gh-pages Branch
|
||||
|
||||
```bash
|
||||
# Create a new orphan branch (no history from main)
|
||||
git checkout --orphan gh-pages
|
||||
|
||||
# Remove all files from staging
|
||||
git rm -rf .
|
||||
```
|
||||
|
||||
**Important:** This creates a completely separate branch. Don't worry - your main branch is safe!
|
||||
|
||||
### 3. Copy APT Repository to Root
|
||||
|
||||
```bash
|
||||
# Copy CONTENTS of apt-repo to root of gh-pages
|
||||
cp -r apt-repo/* .
|
||||
|
||||
# Remove the apt-repo directory itself
|
||||
rm -rf apt-repo
|
||||
|
||||
# Verify the structure
|
||||
ls -la
|
||||
|
||||
# You should see in the current directory:
|
||||
# - dists/
|
||||
# - pool/
|
||||
# - KEY.gpg
|
||||
# - index.html
|
||||
# - README.md
|
||||
```
|
||||
|
||||
**Why root?** GitHub Pages can only serve from:
|
||||
- `/` (root) - what we're doing
|
||||
- `/docs` directory
|
||||
- NOT from custom directories like `/apt-repo`
|
||||
|
||||
### 4. Commit and Push
|
||||
|
||||
```bash
|
||||
# Add all files
|
||||
git add .
|
||||
|
||||
# Commit
|
||||
git commit -m "Initialize APT repository for GitHub Pages"
|
||||
|
||||
# Push to gh-pages branch
|
||||
git push -u origin gh-pages
|
||||
```
|
||||
|
||||
### 5. Return to Main Branch
|
||||
|
||||
```bash
|
||||
# Switch back to your main development branch
|
||||
git checkout main
|
||||
|
||||
# Verify you're back on main
|
||||
git branch
|
||||
# Should show: * main
|
||||
```
|
||||
|
||||
### 6. Enable GitHub Pages
|
||||
|
||||
1. Go to your repository on GitHub
|
||||
2. Click **Settings** (top right)
|
||||
3. Click **Pages** (left sidebar)
|
||||
4. Under "Build and deployment":
|
||||
- Source: **Deploy from a branch**
|
||||
- Branch: **gh-pages**
|
||||
- Folder: **/ (root)**
|
||||
- Click **Save**
|
||||
|
||||
### 7. Wait for Deployment
|
||||
|
||||
GitHub will deploy your site. This usually takes 1-2 minutes.
|
||||
|
||||
You can watch the progress:
|
||||
- Go to **Actions** tab
|
||||
- Look for "pages build and deployment" workflow
|
||||
|
||||
### 8. Verify Your Repository is Live
|
||||
|
||||
Once deployed, your repository will be at:
|
||||
|
||||
```
|
||||
https://YOUR-USERNAME.github.io/socktop/
|
||||
```
|
||||
|
||||
Test it:
|
||||
|
||||
```bash
|
||||
# Check the public key is accessible
|
||||
curl -I https://YOUR-USERNAME.github.io/socktop/KEY.gpg
|
||||
|
||||
# Should return: HTTP/2 200
|
||||
|
||||
# Check the Release file
|
||||
curl -I https://YOUR-USERNAME.github.io/socktop/dists/stable/Release
|
||||
|
||||
# Should return: HTTP/2 200
|
||||
```
|
||||
|
||||
### 9. Test Installation (Optional but Recommended)
|
||||
|
||||
On a Debian/Ubuntu VM or system:
|
||||
|
||||
```bash
|
||||
# Add GPG key
|
||||
curl -fsSL https://YOUR-USERNAME.github.io/socktop/KEY.gpg | \
|
||||
sudo gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg
|
||||
|
||||
# Add repository
|
||||
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://YOUR-USERNAME.github.io/socktop stable main" | \
|
||||
sudo tee /etc/apt/sources.list.d/socktop.list
|
||||
|
||||
# Update package lists
|
||||
sudo apt update
|
||||
|
||||
# You should see:
|
||||
# Get:1 https://YOUR-USERNAME.github.io/socktop stable InRelease [xxx B]
|
||||
|
||||
# Install packages
|
||||
sudo apt install socktop socktop-agent
|
||||
|
||||
# Verify
|
||||
socktop --version
|
||||
```
|
||||
|
||||
## Understanding the Two Branches
|
||||
|
||||
After setup, you'll have two branches:
|
||||
|
||||
### `main` branch (development)
|
||||
```
|
||||
main/
|
||||
├── src/
|
||||
├── Cargo.toml
|
||||
├── scripts/
|
||||
├── docs/
|
||||
├── apt-repo/ ← Local build artifact (not published)
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Purpose:** Source code, development, building packages
|
||||
|
||||
### `gh-pages` branch (published)
|
||||
```
|
||||
gh-pages/
|
||||
├── dists/
|
||||
├── pool/
|
||||
├── KEY.gpg
|
||||
├── index.html ← Customize this for a nice landing page!
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Purpose:** Published APT repository served by GitHub Pages
|
||||
|
||||
## Workflow Going Forward
|
||||
|
||||
### Manual Updates
|
||||
|
||||
When you release a new version:
|
||||
|
||||
```bash
|
||||
# 1. On main branch, build new packages
|
||||
git checkout main
|
||||
cargo deb --package socktop
|
||||
cargo deb --package socktop_agent
|
||||
|
||||
# 2. Update local apt-repo
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop_*.deb
|
||||
./scripts/add-package-to-repo.sh target/debian/socktop-agent_*.deb
|
||||
./scripts/sign-apt-repo.sh apt-repo stable YOUR-GPG-KEY-ID
|
||||
|
||||
# 3. Switch to gh-pages and update
|
||||
git checkout gh-pages
|
||||
cp -r apt-repo/* .
|
||||
git add .
|
||||
git commit -m "Release v1.51.0"
|
||||
git push origin gh-pages
|
||||
|
||||
# 4. Return to main
|
||||
git checkout main
|
||||
```
|
||||
|
||||
### Automated Updates (Recommended)
|
||||
|
||||
Set up GitHub Actions to do this automatically:
|
||||
|
||||
1. Add GitHub Secrets (Settings → Secrets → Actions):
|
||||
- `GPG_PRIVATE_KEY` - Your exported private key
|
||||
- `GPG_KEY_ID` - Your GPG key ID
|
||||
- `GPG_PASSPHRASE` - Your GPG passphrase (if any)
|
||||
|
||||
2. Tag and push:
|
||||
```bash
|
||||
git tag v1.51.0
|
||||
git push origin main --tags
|
||||
```
|
||||
|
||||
3. GitHub Actions will automatically:
|
||||
- Build packages for AMD64 and ARM64
|
||||
- Update apt-repo
|
||||
- Sign with your GPG key
|
||||
- Push to gh-pages
|
||||
- Create GitHub Release
|
||||
|
||||
See `.github/workflows/publish-apt-repo.yml` for details.
|
||||
|
||||
## Customizing Your GitHub Pages Site
|
||||
|
||||
The `gh-pages` branch contains `index.html` which users see when they visit:
|
||||
`https://YOUR-USERNAME.github.io/socktop/`
|
||||
|
||||
You can customize this! On the `gh-pages` branch:
|
||||
|
||||
```bash
|
||||
git checkout gh-pages
|
||||
|
||||
# Edit index.html
|
||||
nano index.html
|
||||
|
||||
# Add features, badges, screenshots, etc.
|
||||
|
||||
git add index.html
|
||||
git commit -m "Improve landing page"
|
||||
git push origin gh-pages
|
||||
|
||||
git checkout main
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "404 Not Found" on GitHub Pages
|
||||
|
||||
**Check:**
|
||||
- Settings → Pages shows "Your site is live at..."
|
||||
- Wait 2-3 minutes after pushing
|
||||
- Verify branch is `gh-pages` and folder is `/`
|
||||
- Check Actions tab for deployment errors
|
||||
|
||||
### "Repository not found" when installing
|
||||
|
||||
**Check:**
|
||||
- URL is correct: `https://USERNAME.github.io/REPO/` (no trailing /apt-repo)
|
||||
- Files exist at the URLs:
|
||||
```bash
|
||||
curl -I https://USERNAME.github.io/REPO/dists/stable/InRelease
|
||||
curl -I https://USERNAME.github.io/REPO/KEY.gpg
|
||||
```
|
||||
|
||||
### "GPG error" when installing
|
||||
|
||||
**Check:**
|
||||
- Repository is signed: `ls gh-pages/dists/stable/Release.gpg`
|
||||
- Users imported the key: `curl https://USERNAME.github.io/REPO/KEY.gpg | gpg --import`
|
||||
|
||||
### Changes not appearing
|
||||
|
||||
**Check:**
|
||||
- You committed and pushed to `gh-pages` (not `main`)
|
||||
- Wait 1-2 minutes for GitHub to redeploy
|
||||
- Clear browser cache if viewing index.html
|
||||
- For apt: `sudo apt clean && sudo apt update`
|
||||
|
||||
## Success Checklist
|
||||
|
||||
After completing this guide, you should have:
|
||||
|
||||
- [ ] `gh-pages` branch created and pushed
|
||||
- [ ] GitHub Pages enabled and deployed
|
||||
- [ ] Site accessible at `https://USERNAME.github.io/socktop/`
|
||||
- [ ] `KEY.gpg` downloadable
|
||||
- [ ] `dists/stable/InRelease` accessible
|
||||
- [ ] Packages in `pool/main/*.deb` downloadable
|
||||
- [ ] Successfully tested installation on a test system
|
||||
- [ ] Understand the workflow for future updates
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Update your main README.md** with installation instructions
|
||||
2. **Set up GitHub Actions** for automated releases
|
||||
3. **Customize index.html** on gh-pages for a nice landing page
|
||||
4. **Test on multiple architectures** (AMD64, ARM64)
|
||||
5. **Share your repository** with users
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Switch branches:**
|
||||
```bash
|
||||
git checkout main # Development
|
||||
git checkout gh-pages # Published site
|
||||
```
|
||||
|
||||
**Update published site manually:**
|
||||
```bash
|
||||
git checkout main
|
||||
# ... build packages, update apt-repo ...
|
||||
git checkout gh-pages
|
||||
cp -r apt-repo/* .
|
||||
git add . && git commit -m "Update" && git push
|
||||
git checkout main
|
||||
```
|
||||
|
||||
**Your repository URL:**
|
||||
```
|
||||
https://YOUR-USERNAME.github.io/socktop/
|
||||
```
|
||||
|
||||
**User installation command:**
|
||||
```bash
|
||||
curl -fsSL https://YOUR-USERNAME.github.io/socktop/KEY.gpg | \
|
||||
sudo gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://YOUR-USERNAME.github.io/socktop stable main" | \
|
||||
sudo tee /etc/apt/sources.list.d/socktop.list
|
||||
sudo apt update && sudo apt install socktop socktop-agent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Need help?** See:
|
||||
- `QUICK_START_APT_REPO.md` - Overall quick start
|
||||
- `docs/APT_REPOSITORY.md` - Comprehensive guide
|
||||
- `docs/APT_WORKFLOW.md` - Visual workflow diagrams
|
||||
@@ -1,119 +0,0 @@
|
||||
# Why We Use the gh-pages Branch
|
||||
|
||||
## The Problem
|
||||
|
||||
GitHub Pages has a limitation - it can only serve static sites from:
|
||||
|
||||
1. **`/` (root)** of a branch
|
||||
2. **`/docs`** directory of a branch
|
||||
3. **NOT** from custom directories like `/apt-repo`
|
||||
|
||||
## Why Not `/docs`?
|
||||
|
||||
When you tried to enable GitHub Pages with `apt-repo/` checked into main, you couldn't select it because:
|
||||
|
||||
```
|
||||
main/
|
||||
├── src/
|
||||
├── Cargo.toml
|
||||
├── apt-repo/ ← GitHub Pages can't serve from here!
|
||||
└── ...
|
||||
```
|
||||
|
||||
You could move it to `/docs`:
|
||||
```
|
||||
main/
|
||||
├── src/
|
||||
├── Cargo.toml
|
||||
├── docs/ ← GitHub Pages CAN serve from here
|
||||
│ ├── dists/
|
||||
│ ├── pool/
|
||||
│ └── ...
|
||||
└── ...
|
||||
```
|
||||
|
||||
But this has downsides:
|
||||
- ❌ Mixed source code and published content
|
||||
- ❌ Large .deb files bloat the main branch
|
||||
- ❌ Can't easily customize the site without affecting source
|
||||
- ❌ Messy git history with binary files
|
||||
|
||||
## Why gh-pages Branch (Our Solution)
|
||||
|
||||
Using a separate `gh-pages` branch is cleaner:
|
||||
|
||||
```
|
||||
main branch (source code):
|
||||
├── src/
|
||||
├── Cargo.toml
|
||||
├── scripts/
|
||||
└── docs/ ← Documentation source
|
||||
|
||||
gh-pages branch (published):
|
||||
├── dists/
|
||||
├── pool/
|
||||
├── KEY.gpg
|
||||
├── index.html ← Customizable landing page
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
✅ **Clean separation**: Source code stays in `main`, published content in `gh-pages`
|
||||
✅ **No binary bloat**: .deb files don't clutter your main branch history
|
||||
✅ **Easy automation**: GitHub Actions can push to gh-pages without affecting main
|
||||
✅ **Customizable**: You can make a beautiful landing page on gh-pages
|
||||
✅ **Standard practice**: Most GitHub Pages projects use gh-pages branch
|
||||
✅ **Root URL**: Your repo is at `https://username.github.io/socktop/` (not `/apt-repo`)
|
||||
|
||||
### Workflow
|
||||
|
||||
```
|
||||
Developer (main branch)
|
||||
↓
|
||||
Build packages
|
||||
↓
|
||||
Update apt-repo/ (local)
|
||||
↓
|
||||
Push to gh-pages branch
|
||||
↓
|
||||
GitHub Pages serves
|
||||
↓
|
||||
Users: apt install socktop
|
||||
```
|
||||
|
||||
## The Setup
|
||||
|
||||
**One-time setup:**
|
||||
```bash
|
||||
git checkout --orphan gh-pages
|
||||
git rm -rf .
|
||||
cp -r apt-repo/* .
|
||||
rm -rf apt-repo
|
||||
git add . && git commit -m "Initialize APT repository"
|
||||
git push -u origin gh-pages
|
||||
git checkout main
|
||||
```
|
||||
|
||||
**Going forward:**
|
||||
- Work on `main` branch for development
|
||||
- `gh-pages` branch gets updated by GitHub Actions (or manually)
|
||||
- Never need to switch branches manually after automation is set up!
|
||||
|
||||
## Comparison
|
||||
|
||||
| Approach | Location | URL | Pros | Cons |
|
||||
|----------|----------|-----|------|------|
|
||||
| **gh-pages branch** ✅ | gh-pages:/ | `username.github.io/socktop/` | Clean, automated, customizable | Two branches |
|
||||
| `/docs` on main | main:/docs | `username.github.io/socktop/` | One branch | Mixed content, binary bloat |
|
||||
| `/apt-repo` on main | main:/apt-repo | ❌ Not possible | - | GitHub Pages won't allow it |
|
||||
|
||||
## Conclusion
|
||||
|
||||
The `gh-pages` branch approach is:
|
||||
- The **cleanest** solution
|
||||
- The **most flexible** for customization
|
||||
- The **easiest to automate**
|
||||
- **Industry standard** for GitHub Pages
|
||||
|
||||
That's why we chose it! 🚀
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,43 +0,0 @@
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA512
|
||||
|
||||
Origin: socktop
|
||||
Label: socktop
|
||||
Suite: stable
|
||||
Codename: stable
|
||||
Architectures: amd64 arm64 armhf riscv64
|
||||
Components: main
|
||||
Description: socktop APT repository
|
||||
Date: Mon, 24 Aug 2026 14:30:55 +0000
|
||||
MD5Sum:
|
||||
ada07cbd3ac088cecb5dc9d08518823d 1627 main/binary-amd64/Packages
|
||||
fbb8924917960d65b5bcad003ccb90c2 830 main/binary-amd64/Packages.gz
|
||||
71b8d2e69d19d4db385bc3f2be603b72 1627 main/binary-arm64/Packages
|
||||
70831c731ff47542094fe3417e385aa6 830 main/binary-arm64/Packages.gz
|
||||
129d1b34bf48c75861a188d6ae8fba77 1611 main/binary-armhf/Packages
|
||||
54e691233b47bb1f0d89f82b00d9403f 814 main/binary-armhf/Packages.gz
|
||||
4c3fc0b399405dd682dac1318adda05f 1623 main/binary-riscv64/Packages
|
||||
15bcbe1806bf2577849090556c3a2a81 817 main/binary-riscv64/Packages.gz
|
||||
SHA256:
|
||||
88a907a84f4d292e34426d71e328561aba9658440e588f62f0f3b5d7f5c5f121 1627 main/binary-amd64/Packages
|
||||
3a87b0b38cb79d231b8ed66e1fdc9a54369d0d3f2f0686f6513cece15f0dc967 830 main/binary-amd64/Packages.gz
|
||||
84a43993bf58648174ef5bf1d4542e96f5d7903acedc0405f8f3aeab4fdbb4e7 1627 main/binary-arm64/Packages
|
||||
fd8e6a7ec2b7c438659b0fe8fa825670ccfbeb2298c01b55020cbdad2acb3313 830 main/binary-arm64/Packages.gz
|
||||
63a89cdee8e5c93867b2dd5193df162304ea97855ad68135c5c5d0753700102c 1611 main/binary-armhf/Packages
|
||||
c81f19a798757bd1806bb860aba238757edf1b902338554e30bcddba86b3c02b 814 main/binary-armhf/Packages.gz
|
||||
fa590edf01fbdd63817bd1cee4ab49d93bd47d60d74ecdae961cfa927e5b0526 1623 main/binary-riscv64/Packages
|
||||
3815e37b15c4ac150e23ce76786bfda0165e325d00b3650466442356ac39221b 817 main/binary-riscv64/Packages.gz
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQGzBAEBCgAdFiEEHnVWqAU5uDlLwoINESwaeYRl+/IFAmqMVaEACgkQESwaeYRl
|
||||
+/JqUQv+PFvgvw2yMwX1jfCeyKAfc/BmWIitVo5eOYS4P4iMqnOLGP9M44okbhgH
|
||||
Ss7l9kg13Bmtn3TdF6eWOoyfhb/Zchpf5O4xeaXLCBsaTjFtX2BAgg7Z441AkEVP
|
||||
AzaGcmbev6+R7EM3EEqk0fvVLyYHWw9qY9ADC+ArZ7GxYgVgAkIcM2V1bzjJQtOw
|
||||
dYR2XwGWiFZet62DXVsCs6g26tUyvmR062GcKmk1xQY/g9SbOqPXYP7Uou5hw+UZ
|
||||
QAY9LFnCWDz0OATclIKZzqhp7oMjWe3T96d/25lV+fQ2Cmk7ayPGaTrpeTnvCfzO
|
||||
u46DIQkkTpyalhZPcC0hgCg9tbh2rzAF6ETy+oKmYYE8wEA8wqIvJdGt1RZT/IUg
|
||||
SQEma61GavK0nBVjPjEjchT5hfTgS03YBea15lA5sZiEAD0SOV4QHxbbVmszGEaQ
|
||||
Ha4AnRLBji9Ea5E3848QTJLPq1BZ7zLvU6Tn8Nlutuh2doizSQlX3xSozvUV9BYp
|
||||
bWU/J6Ye
|
||||
=8uE3
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -1,26 +0,0 @@
|
||||
Origin: socktop
|
||||
Label: socktop
|
||||
Suite: stable
|
||||
Codename: stable
|
||||
Architectures: amd64 arm64 armhf riscv64
|
||||
Components: main
|
||||
Description: socktop APT repository
|
||||
Date: Mon, 24 Aug 2026 14:30:55 +0000
|
||||
MD5Sum:
|
||||
ada07cbd3ac088cecb5dc9d08518823d 1627 main/binary-amd64/Packages
|
||||
fbb8924917960d65b5bcad003ccb90c2 830 main/binary-amd64/Packages.gz
|
||||
71b8d2e69d19d4db385bc3f2be603b72 1627 main/binary-arm64/Packages
|
||||
70831c731ff47542094fe3417e385aa6 830 main/binary-arm64/Packages.gz
|
||||
129d1b34bf48c75861a188d6ae8fba77 1611 main/binary-armhf/Packages
|
||||
54e691233b47bb1f0d89f82b00d9403f 814 main/binary-armhf/Packages.gz
|
||||
4c3fc0b399405dd682dac1318adda05f 1623 main/binary-riscv64/Packages
|
||||
15bcbe1806bf2577849090556c3a2a81 817 main/binary-riscv64/Packages.gz
|
||||
SHA256:
|
||||
88a907a84f4d292e34426d71e328561aba9658440e588f62f0f3b5d7f5c5f121 1627 main/binary-amd64/Packages
|
||||
3a87b0b38cb79d231b8ed66e1fdc9a54369d0d3f2f0686f6513cece15f0dc967 830 main/binary-amd64/Packages.gz
|
||||
84a43993bf58648174ef5bf1d4542e96f5d7903acedc0405f8f3aeab4fdbb4e7 1627 main/binary-arm64/Packages
|
||||
fd8e6a7ec2b7c438659b0fe8fa825670ccfbeb2298c01b55020cbdad2acb3313 830 main/binary-arm64/Packages.gz
|
||||
63a89cdee8e5c93867b2dd5193df162304ea97855ad68135c5c5d0753700102c 1611 main/binary-armhf/Packages
|
||||
c81f19a798757bd1806bb860aba238757edf1b902338554e30bcddba86b3c02b 814 main/binary-armhf/Packages.gz
|
||||
fa590edf01fbdd63817bd1cee4ab49d93bd47d60d74ecdae961cfa927e5b0526 1623 main/binary-riscv64/Packages
|
||||
3815e37b15c4ac150e23ce76786bfda0165e325d00b3650466442356ac39221b 817 main/binary-riscv64/Packages.gz
|
||||
@@ -1,14 +0,0 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQGzBAABCgAdFiEEHnVWqAU5uDlLwoINESwaeYRl+/IFAmqMVZ8ACgkQESwaeYRl
|
||||
+/JgAgwA1xKy+N7n4S4bjP1LxoLDa0fOHnxORd2dajYHkA6NqORSd2KPgWqBwtlA
|
||||
TGA8wJ8Gurdluu6kaZ8rJ55PUVZFX2qU/UsXy0Pn1Kp2NMuMhdkzXUuYI/oKILof
|
||||
TtYKqubJ4+LZvfrCdOWusDGhybA4DXaPGm+/EjJ6xKJDm0N6Ht4YhUlb9uTFH8jr
|
||||
y/hzl9Qcp2GoaDIXoLQCYPfUePHSH7JRn77uuKu3DKC3rwP2QXyBDNfWRJY0X89L
|
||||
fGBzAxgj2jE7/Gn/T3X3QGTsSAlZeuEIu88/+qwyi3kq7SeBWFf70kGHFS3RPAmh
|
||||
ULMpQe44N0H3faBIC1wER/P12NIDCi633XkL5WVwA+HvSWk8DZGu+cV4s7BcmWbD
|
||||
8KOcJe9dEoYpcGbxpZMqe0iCc6pr7vQ5FBlt0Zj0LzvONSL55kH8L7tG/uX4VXcM
|
||||
g6LLLJjm6oLWT8U8QNskOgAVxTgyZgHUVa2cAG0jZp0BErSegg38mHLs3QXtdySc
|
||||
xd4t41hM
|
||||
=OG4V
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -1,40 +0,0 @@
|
||||
Package: socktop
|
||||
Version: 1.60.2-1
|
||||
Architecture: amd64
|
||||
Maintainer: Jason Witty <jasonpwitty+socktop@proton.me>
|
||||
Installed-Size: 3441
|
||||
Depends: libc6 (>= 2.34)
|
||||
Filename: pool/main/socktop_1.60.2-1_amd64.deb
|
||||
Size: 1313748
|
||||
MD5sum: 24517013f7e9683f2bbbbf07debdffca
|
||||
SHA1: c5c9498bfcc8a8d21342f1f4b5797e4c000462d7
|
||||
SHA256: b5d21f5f322af61e7ea6846ba337e5e48697969cf40b098d01ad4927c912d6e6
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Homepage: https://github.com/jasonwitty/socktop
|
||||
Description: Remote system monitor over WebSocket, TUI like top
|
||||
socktop is a remote system monitor with a rich terminal user interface (TUI)
|
||||
that connects to remote hosts running the socktop_agent over WebSocket. It
|
||||
provides real-time monitoring of CPU, memory, processes, and more with an
|
||||
interface similar to the traditional 'top' command.
|
||||
|
||||
Package: socktop-agent
|
||||
Version: 1.60.2-1
|
||||
Architecture: amd64
|
||||
Maintainer: Jason Witty <jasonpwitty+socktop@proton.me>
|
||||
Installed-Size: 6537
|
||||
Depends: libc6 (>= 2.34), libdrm-amdgpu1 (>= 2.4.80)
|
||||
Filename: pool/main/socktop-agent_1.60.2-1_amd64.deb
|
||||
Size: 1858208
|
||||
MD5sum: fa4f1a29b22fe191140787da50c70cd1
|
||||
SHA1: b310e7f5a29be4d18add88a6b30437eb34d2e6a1
|
||||
SHA256: 328975156e28ffc2d38a23c317406b9220f0062992f97bfea327403ba6d26d71
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Homepage: https://github.com/jasonwitty/socktop
|
||||
Description: Socktop agent daemon. Serves host metrics over WebSocket.
|
||||
socktop_agent is the daemon component that runs on remote hosts to collect and
|
||||
serve system metrics over WebSocket. It gathers CPU, memory, disk, network,
|
||||
GPU, and process information that can be monitored remotely by the socktop TUI
|
||||
client.
|
||||
|
||||
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
Archive: stable
|
||||
Component: main
|
||||
Origin: socktop
|
||||
Label: socktop
|
||||
Architecture: amd64
|
||||
@@ -1,40 +0,0 @@
|
||||
Package: socktop
|
||||
Version: 1.60.2-1
|
||||
Architecture: arm64
|
||||
Maintainer: Jason Witty <jasonpwitty+socktop@proton.me>
|
||||
Installed-Size: 2994
|
||||
Depends: libc6 (>= 2.34)
|
||||
Filename: pool/main/socktop_1.60.2-1_arm64.deb
|
||||
Size: 1176428
|
||||
MD5sum: 6c628e55b118550964fdd3429ade9013
|
||||
SHA1: 814407491af72cc8c03dfc4aca1fcd29676d84e7
|
||||
SHA256: fb3f29fe439d7e8a6813c52811a4acb9dc5a390acbbe5c8b5a7d77301bf7df39
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Homepage: https://github.com/jasonwitty/socktop
|
||||
Description: Remote system monitor over WebSocket, TUI like top
|
||||
socktop is a remote system monitor with a rich terminal user interface (TUI)
|
||||
that connects to remote hosts running the socktop_agent over WebSocket. It
|
||||
provides real-time monitoring of CPU, memory, processes, and more with an
|
||||
interface similar to the traditional 'top' command.
|
||||
|
||||
Package: socktop-agent
|
||||
Version: 1.60.2-1
|
||||
Architecture: arm64
|
||||
Maintainer: Jason Witty <jasonpwitty+socktop@proton.me>
|
||||
Installed-Size: 5158
|
||||
Depends: libc6 (>= 2.34), libdrm-amdgpu1 (>= 2.4.80)
|
||||
Filename: pool/main/socktop-agent_1.60.2-1_arm64.deb
|
||||
Size: 1648768
|
||||
MD5sum: d40c11e1a82a4deee69a8f09b99b8a3c
|
||||
SHA1: bb17940a085d9cdda0d0ae26aa73843f3b293705
|
||||
SHA256: 9f0779dafe9e2c5029c65201e2d45abb2cc9b9dfae38deb6d650f6c9a5fd07ce
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Homepage: https://github.com/jasonwitty/socktop
|
||||
Description: Socktop agent daemon. Serves host metrics over WebSocket.
|
||||
socktop_agent is the daemon component that runs on remote hosts to collect and
|
||||
serve system metrics over WebSocket. It gathers CPU, memory, disk, network,
|
||||
GPU, and process information that can be monitored remotely by the socktop TUI
|
||||
client.
|
||||
|
||||
Binary file not shown.
@@ -1,40 +0,0 @@
|
||||
Package: socktop
|
||||
Version: 1.60.2-1
|
||||
Architecture: armhf
|
||||
Maintainer: Jason Witty <jasonpwitty+socktop@proton.me>
|
||||
Installed-Size: 2764
|
||||
Depends: libc6:armhf (>= 2.35)
|
||||
Filename: pool/main/socktop_1.60.2-1_armhf.deb
|
||||
Size: 1029920
|
||||
MD5sum: 731d71c8450006624db51f802a1b75fa
|
||||
SHA1: 7a797ecb089208171bc762a2602af8048b9d74fa
|
||||
SHA256: bfd6d29c18826ed20a9f0e3af2e0f5cc2d24dd23f621110f9d58aed60f7ca1a5
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Homepage: https://github.com/jasonwitty/socktop
|
||||
Description: Remote system monitor over WebSocket, TUI like top
|
||||
socktop is a remote system monitor with a rich terminal user interface (TUI)
|
||||
that connects to remote hosts running the socktop_agent over WebSocket. It
|
||||
provides real-time monitoring of CPU, memory, processes, and more with an
|
||||
interface similar to the traditional 'top' command.
|
||||
|
||||
Package: socktop-agent
|
||||
Version: 1.60.2-1
|
||||
Architecture: armhf
|
||||
Maintainer: Jason Witty <jasonpwitty+socktop@proton.me>
|
||||
Installed-Size: 3821
|
||||
Depends: libc6:armhf (>= 2.35)
|
||||
Filename: pool/main/socktop-agent_1.60.2-1_armhf.deb
|
||||
Size: 1462340
|
||||
MD5sum: b987e05b88a9a32fb3379c54413a1eb0
|
||||
SHA1: c751d0bf6ced067c667895f36379df4bc7b1170c
|
||||
SHA256: c2bcce2e679d96cc9076d118abd9a766ed413f306d88dcc59912814c1962ba04
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Homepage: https://github.com/jasonwitty/socktop
|
||||
Description: Socktop agent daemon. Serves host metrics over WebSocket.
|
||||
socktop_agent is the daemon component that runs on remote hosts to collect and
|
||||
serve system metrics over WebSocket. It gathers CPU, memory, disk, network,
|
||||
GPU, and process information that can be monitored remotely by the socktop TUI
|
||||
client.
|
||||
|
||||
Binary file not shown.
@@ -1,40 +0,0 @@
|
||||
Package: socktop
|
||||
Version: 1.60.2-1
|
||||
Architecture: riscv64
|
||||
Maintainer: Jason Witty <jasonpwitty+socktop@proton.me>
|
||||
Installed-Size: 2668
|
||||
Depends: libc6:riscv64 (>= 2.35)
|
||||
Filename: pool/main/socktop_1.60.2-1_riscv64.deb
|
||||
Size: 1166192
|
||||
MD5sum: 11e89c1f7909fc90047a74d2880d1362
|
||||
SHA1: 4089962ff3864209a026b585fe2b6490cfe014a2
|
||||
SHA256: e1fad743c74934a11e49808d0bba80c26f05e6d75c231f239870866ba50e1e38
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Homepage: https://github.com/jasonwitty/socktop
|
||||
Description: Remote system monitor over WebSocket, TUI like top
|
||||
socktop is a remote system monitor with a rich terminal user interface (TUI)
|
||||
that connects to remote hosts running the socktop_agent over WebSocket. It
|
||||
provides real-time monitoring of CPU, memory, processes, and more with an
|
||||
interface similar to the traditional 'top' command.
|
||||
|
||||
Package: socktop-agent
|
||||
Version: 1.60.2-1
|
||||
Architecture: riscv64
|
||||
Maintainer: Jason Witty <jasonpwitty+socktop@proton.me>
|
||||
Installed-Size: 3846
|
||||
Depends: libc6:riscv64 (>= 2.35)
|
||||
Filename: pool/main/socktop-agent_1.60.2-1_riscv64.deb
|
||||
Size: 1637096
|
||||
MD5sum: 98347f203e942073464ff8b8d16796bb
|
||||
SHA1: dec2ae6fdd5bea71f43f9e24842a0851150e24fd
|
||||
SHA256: fd4070c6e0b29adf5bb9de32f098431e9dcf7456e1fc7f4460a5e9a5708496cc
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Homepage: https://github.com/jasonwitty/socktop
|
||||
Description: Socktop agent daemon. Serves host metrics over WebSocket.
|
||||
socktop_agent is the daemon component that runs on remote hosts to collect and
|
||||
serve system metrics over WebSocket. It gathers CPU, memory, disk, network,
|
||||
GPU, and process information that can be monitored remotely by the socktop TUI
|
||||
client.
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Socktop agent
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/socktop_agent --port 3000
|
||||
Environment=RUST_LOG=info
|
||||
# Optional auth:
|
||||
# Environment=SOCKTOP_TOKEN=changeme
|
||||
Restart=on-failure
|
||||
User=socktop
|
||||
Group=socktop
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
-364
@@ -1,364 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>socktop APT Repository</title>
|
||||
<style>
|
||||
/* Catppuccin Frappe Color Palette */
|
||||
:root {
|
||||
--ctp-base: #303446;
|
||||
--ctp-mantle: #292c3c;
|
||||
--ctp-crust: #232634;
|
||||
--ctp-text: #c6d0f5;
|
||||
--ctp-subtext1: #b5bfe2;
|
||||
--ctp-subtext0: #a5adce;
|
||||
--ctp-overlay2: #949cbb;
|
||||
--ctp-overlay1: #838ba7;
|
||||
--ctp-overlay0: #737994;
|
||||
--ctp-surface2: #626880;
|
||||
--ctp-surface1: #51576d;
|
||||
--ctp-surface0: #414559;
|
||||
--ctp-lavender: #babbf1;
|
||||
--ctp-blue: #8caaee;
|
||||
--ctp-sapphire: #85c1dc;
|
||||
--ctp-sky: #99d1db;
|
||||
--ctp-teal: #81c8be;
|
||||
--ctp-green: #a6d189;
|
||||
--ctp-yellow: #e5c890;
|
||||
--ctp-peach: #ef9f76;
|
||||
--ctp-maroon: #ea999c;
|
||||
--ctp-red: #e78284;
|
||||
--ctp-mauve: #ca9ee6;
|
||||
--ctp-pink: #f4b8e4;
|
||||
--ctp-flamingo: #eebebe;
|
||||
--ctp-rosewater: #f2d5cf;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
background-color: var(--ctp-base);
|
||||
color: var(--ctp-text);
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background-color: var(--ctp-mantle);
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--ctp-blue);
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 3px solid var(--ctp-surface0);
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
h2 {
|
||||
color: var(--ctp-mauve);
|
||||
font-size: 1.8em;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
h3 {
|
||||
color: var(--ctp-sapphire);
|
||||
font-size: 1.3em;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
color: var(--ctp-subtext0);
|
||||
}
|
||||
.subtitle {
|
||||
color: var(--ctp-subtext1);
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--ctp-surface0);
|
||||
color: var(--ctp-green);
|
||||
padding: 3px 8px;
|
||||
border-radius: 5px;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
pre {
|
||||
background-color: var(--ctp-crust);
|
||||
border: 1px solid var(--ctp-surface0);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
overflow-x: auto;
|
||||
margin: 15px 0;
|
||||
position: relative;
|
||||
padding-top: 18px; /* leave space for top-right button */
|
||||
}
|
||||
pre code {
|
||||
background: transparent;
|
||||
color: var(--ctp-text);
|
||||
display: block;
|
||||
white-space: pre;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
/* Copy button styles */
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background: var(--ctp-surface1);
|
||||
color: var(--ctp-text);
|
||||
border: 1px solid var(--ctp-surface2);
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition:
|
||||
background 0.12s ease,
|
||||
transform 0.08s ease;
|
||||
}
|
||||
.copy-btn:hover {
|
||||
background: var(--ctp-surface2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.copy-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.copy-btn.copied {
|
||||
background: var(--ctp-green);
|
||||
color: var(--ctp-crust);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
background-color: var(--ctp-surface1);
|
||||
color: var(--ctp-text);
|
||||
padding: 5px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9em;
|
||||
margin: 5px 5px 5px 0;
|
||||
border: 1px solid var(--ctp-surface2);
|
||||
}
|
||||
.badge.arch {
|
||||
background-color: var(--ctp-surface0);
|
||||
color: var(--ctp-lavender);
|
||||
}
|
||||
|
||||
.note {
|
||||
background-color: var(--ctp-surface0);
|
||||
border-left: 4px solid var(--ctp-yellow);
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.note strong {
|
||||
color: var(--ctp-yellow);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 50px;
|
||||
padding-top: 20px;
|
||||
border-top: 2px solid var(--ctp-surface0);
|
||||
text-align: center;
|
||||
color: var(--ctp-overlay1);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.command-comment {
|
||||
color: var(--ctp-overlay1);
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.highlight-blue {
|
||||
color: var(--ctp-blue);
|
||||
}
|
||||
.highlight-green {
|
||||
color: var(--ctp-green);
|
||||
}
|
||||
.highlight-yellow {
|
||||
color: var(--ctp-yellow);
|
||||
}
|
||||
.highlight-mauve {
|
||||
color: var(--ctp-mauve);
|
||||
}
|
||||
.highlight-peach {
|
||||
color: var(--ctp-peach);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 25px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>socktop APT Repository</h1>
|
||||
<p class="subtitle">
|
||||
System monitor with remote agent support for Linux systems
|
||||
</p>
|
||||
|
||||
<h2>📦 Quick Installation</h2>
|
||||
<p>Add this repository to your Debian/Ubuntu system:</p>
|
||||
|
||||
<h3>Step 1: Add GPG Key</h3>
|
||||
<pre><code># Add the repository's GPG signing key
|
||||
curl -fsSL https://jasonwitty.github.io/socktop/KEY.gpg | \
|
||||
sudo gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg</code></pre>
|
||||
|
||||
<h3>Step 2: Add Repository</h3>
|
||||
<pre><code># Add the APT repository to your sources
|
||||
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://jasonwitty.github.io/socktop stable main" | \
|
||||
sudo tee /etc/apt/sources.list.d/socktop.list</code></pre>
|
||||
|
||||
<h3>Step 3: Install</h3>
|
||||
<pre><code># Update package lists and install
|
||||
sudo apt update
|
||||
sudo apt install socktop socktop-agent</code></pre>
|
||||
|
||||
<h2>📋 What's Included</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<strong class="highlight-blue">socktop</strong> - Terminal
|
||||
UI client for monitoring systems
|
||||
</li>
|
||||
<li>
|
||||
<strong class="highlight-mauve">socktop-agent</strong> -
|
||||
Background agent that reports system metrics
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="note">
|
||||
<strong>Note:</strong> The agent package automatically installs
|
||||
and configures a systemd service. Enable it with:
|
||||
<code>sudo systemctl enable --now socktop-agent</code>
|
||||
</div>
|
||||
|
||||
<h2>🏗️ Supported Architectures</h2>
|
||||
<div>
|
||||
<span class="badge arch">amd64</span>
|
||||
<span class="badge arch">arm64</span>
|
||||
<span class="badge arch">armhf</span>
|
||||
<span class="badge arch">riscv64</span>
|
||||
</div>
|
||||
|
||||
<h2>🔧 Usage</h2>
|
||||
<p>After installation:</p>
|
||||
<pre><code># Start the TUI client
|
||||
socktop
|
||||
|
||||
# Connect to a remote agent
|
||||
socktop ws://hostname:3000
|
||||
|
||||
# Start the agent (if not using systemd)
|
||||
socktop_agent</code></pre>
|
||||
|
||||
<h2>🔗 Links</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/jasonwitty/socktop"
|
||||
>Source Code on GitHub</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/jasonwitty/socktop/issues"
|
||||
>Report Issues</a
|
||||
>
|
||||
</li>
|
||||
<li><a href="README.md">Repository Documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="footer">
|
||||
<p>Hosted on GitHub Pages | Packages signed with GPG</p>
|
||||
<p>
|
||||
Theme:
|
||||
<a href="https://github.com/catppuccin/catppuccin"
|
||||
>Catppuccin Frappe</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Attach copy buttons to all <pre> blocks and enable copy-to-clipboard.
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const pres = document.querySelectorAll("pre");
|
||||
pres.forEach((pre) => {
|
||||
// Create button
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "copy-btn";
|
||||
btn.setAttribute("aria-label", "Copy code to clipboard");
|
||||
// Use emoji for simplicity; you can replace with SVG if desired
|
||||
btn.innerText = "📋";
|
||||
|
||||
// Append to pre
|
||||
pre.appendChild(btn);
|
||||
|
||||
// Click handler
|
||||
btn.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
const code = pre.querySelector("code");
|
||||
const text = code ? code.innerText : pre.innerText;
|
||||
try {
|
||||
if (!navigator.clipboard) {
|
||||
// Fallback method
|
||||
const textarea =
|
||||
document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.left = "-9999px";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textarea);
|
||||
} else {
|
||||
await navigator.clipboard.writeText(text);
|
||||
}
|
||||
// feedback
|
||||
btn.classList.add("copied");
|
||||
const prior = btn.innerText;
|
||||
btn.innerText = "✓ Copied";
|
||||
setTimeout(() => {
|
||||
btn.classList.remove("copied");
|
||||
btn.innerText = "📋";
|
||||
}, 1800);
|
||||
} catch (err) {
|
||||
btn.innerText = "✖";
|
||||
setTimeout(() => (btn.innerText = "📋"), 1500);
|
||||
console.error("Copy failed", err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "socktop"
|
||||
version = "0.1.0"
|
||||
authors = ["Jason Witty <jasonpwitty+socktop@proton.me>"]
|
||||
description = "Remote system monitor over WebSocket, TUI like top"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true }
|
||||
tokio-tungstenite = { workspace = true }
|
||||
tungstenite = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
url = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
@@ -0,0 +1,198 @@
|
||||
//! App state and main loop: input handling, fetching metrics, updating history, and drawing.
|
||||
|
||||
use std::{collections::VecDeque, io, time::{Duration, Instant}};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Constraint, Direction},
|
||||
Terminal,
|
||||
};
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::history::{push_capped, PerCoreHistory};
|
||||
use crate::types::Metrics;
|
||||
use crate::ui::{header::draw_header, cpu::{draw_cpu_avg_graph, draw_per_core_bars}, mem::draw_mem, swap::draw_swap, disks::draw_disks, net::draw_net_spark, processes::draw_top_processes};
|
||||
use crate::ws::{connect, request_metrics};
|
||||
|
||||
pub struct App {
|
||||
// Latest metrics + histories
|
||||
last_metrics: Option<Metrics>,
|
||||
|
||||
// CPU avg history (0..100)
|
||||
cpu_hist: VecDeque<u64>,
|
||||
|
||||
// Per-core history (0..100)
|
||||
per_core_hist: PerCoreHistory,
|
||||
|
||||
// Network totals snapshot + histories of KB/s
|
||||
last_net_totals: Option<(u64, u64, Instant)>,
|
||||
rx_hist: VecDeque<u64>,
|
||||
tx_hist: VecDeque<u64>,
|
||||
rx_peak: u64,
|
||||
tx_peak: u64,
|
||||
|
||||
// Quit flag
|
||||
should_quit: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
last_metrics: None,
|
||||
cpu_hist: VecDeque::with_capacity(600),
|
||||
per_core_hist: PerCoreHistory::new(60),
|
||||
last_net_totals: None,
|
||||
rx_hist: VecDeque::with_capacity(600),
|
||||
tx_hist: VecDeque::with_capacity(600),
|
||||
rx_peak: 0,
|
||||
tx_peak: 0,
|
||||
should_quit: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self, url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Connect to agent
|
||||
let mut ws = connect(url).await?;
|
||||
|
||||
// Terminal setup
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.clear()?;
|
||||
|
||||
// Main loop
|
||||
let res = self.event_loop(&mut terminal, &mut ws).await;
|
||||
|
||||
// Teardown
|
||||
disable_raw_mode()?;
|
||||
let backend = terminal.backend_mut();
|
||||
execute!(backend, LeaveAlternateScreen)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
async fn event_loop<B: ratatui::backend::Backend>(
|
||||
&mut self,
|
||||
terminal: &mut Terminal<B>,
|
||||
ws: &mut crate::ws::WsStream,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
loop {
|
||||
// Input (non-blocking)
|
||||
while event::poll(Duration::from_millis(10))? {
|
||||
if let Event::Key(k) = event::read()? {
|
||||
if matches!(k.code, KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc) {
|
||||
self.should_quit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.should_quit {
|
||||
break;
|
||||
}
|
||||
|
||||
// Fetch and update
|
||||
if let Some(m) = request_metrics(ws).await {
|
||||
self.update_with_metrics(m);
|
||||
}
|
||||
|
||||
// Draw
|
||||
terminal.draw(|f| self.draw(f))?;
|
||||
|
||||
// Tick rate
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_with_metrics(&mut self, m: Metrics) {
|
||||
// CPU avg history
|
||||
let v = m.cpu_total.clamp(0.0, 100.0).round() as u64;
|
||||
push_capped(&mut self.cpu_hist, v, 600);
|
||||
|
||||
// Per-core history (push current samples)
|
||||
self.per_core_hist.ensure_cores(m.cpu_per_core.len());
|
||||
self.per_core_hist.push_samples(&m.cpu_per_core);
|
||||
|
||||
// NET: sum across all ifaces, compute KB/s via elapsed time
|
||||
let now = Instant::now();
|
||||
let rx_total = m.networks.iter().map(|n| n.received).sum::<u64>();
|
||||
let tx_total = m.networks.iter().map(|n| n.transmitted).sum::<u64>();
|
||||
let (rx_kb, tx_kb) = if let Some((prx, ptx, pts)) = self.last_net_totals {
|
||||
let dt = now.duration_since(pts).as_secs_f64().max(1e-6);
|
||||
let rx = ((rx_total.saturating_sub(prx)) as f64 / dt / 1024.0).round() as u64;
|
||||
let tx = ((tx_total.saturating_sub(ptx)) as f64 / dt / 1024.0).round() as u64;
|
||||
(rx, tx)
|
||||
} else { (0, 0) };
|
||||
self.last_net_totals = Some((rx_total, tx_total, now));
|
||||
push_capped(&mut self.rx_hist, rx_kb, 600);
|
||||
push_capped(&mut self.tx_hist, tx_kb, 600);
|
||||
self.rx_peak = self.rx_peak.max(rx_kb);
|
||||
self.tx_peak = self.tx_peak.max(tx_kb);
|
||||
|
||||
self.last_metrics = Some(m);
|
||||
}
|
||||
|
||||
fn draw(&mut self, f: &mut ratatui::Frame<'_>) {
|
||||
let area = f.area();
|
||||
|
||||
let rows = ratatui::layout::Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(1),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Min(10),
|
||||
])
|
||||
.split(area);
|
||||
|
||||
draw_header(f, rows[0], self.last_metrics.as_ref());
|
||||
|
||||
let top = ratatui::layout::Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(66), Constraint::Percentage(34)])
|
||||
.split(rows[1]);
|
||||
|
||||
draw_cpu_avg_graph(f, top[0], &self.cpu_hist, self.last_metrics.as_ref());
|
||||
draw_per_core_bars(f, top[1], self.last_metrics.as_ref(), &self.per_core_hist);
|
||||
|
||||
draw_mem(f, rows[2], self.last_metrics.as_ref());
|
||||
draw_swap(f, rows[3], self.last_metrics.as_ref());
|
||||
|
||||
let bottom = ratatui::layout::Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(66), Constraint::Percentage(34)])
|
||||
.split(rows[4]);
|
||||
|
||||
let left_stack = ratatui::layout::Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(6), Constraint::Length(4), Constraint::Length(4)])
|
||||
.split(bottom[0]);
|
||||
|
||||
draw_disks(f, left_stack[0], self.last_metrics.as_ref());
|
||||
draw_net_spark(
|
||||
f,
|
||||
left_stack[1],
|
||||
&format!("Download (KB/s) — now: {} | peak: {}", self.rx_hist.back().copied().unwrap_or(0), self.rx_peak),
|
||||
&self.rx_hist,
|
||||
ratatui::style::Color::Green,
|
||||
);
|
||||
draw_net_spark(
|
||||
f,
|
||||
left_stack[2],
|
||||
&format!("Upload (KB/s) — now: {} | peak: {}", self.tx_hist.back().copied().unwrap_or(0), self.tx_peak),
|
||||
&self.tx_hist,
|
||||
ratatui::style::Color::Blue,
|
||||
);
|
||||
|
||||
draw_top_processes(f, bottom[1], self.last_metrics.as_ref());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//! Small utilities to manage bounded history buffers for charts.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub fn push_capped<T>(dq: &mut VecDeque<T>, v: T, cap: usize) {
|
||||
if dq.len() == cap {
|
||||
dq.pop_front();
|
||||
}
|
||||
dq.push_back(v);
|
||||
}
|
||||
|
||||
// Keeps a history deque per core with a fixed capacity
|
||||
pub struct PerCoreHistory {
|
||||
pub deques: Vec<VecDeque<u16>>,
|
||||
cap: usize,
|
||||
}
|
||||
|
||||
impl PerCoreHistory {
|
||||
pub fn new(cap: usize) -> Self {
|
||||
Self { deques: Vec::new(), cap }
|
||||
}
|
||||
|
||||
// Ensure we have one deque per core; resize on CPU topology changes
|
||||
pub fn ensure_cores(&mut self, n: usize) {
|
||||
if self.deques.len() == n {
|
||||
return;
|
||||
}
|
||||
self.deques = (0..n).map(|_| VecDeque::with_capacity(self.cap)).collect();
|
||||
}
|
||||
|
||||
// Push a new sample set for all cores (values 0..=100)
|
||||
pub fn push_samples(&mut self, samples: &[f32]) {
|
||||
self.ensure_cores(samples.len());
|
||||
for (i, v) in samples.iter().enumerate() {
|
||||
let val = v.clamp(0.0, 100.0).round() as u16;
|
||||
push_capped(&mut self.deques[i], val, self.cap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//! Entry point for the socktop TUI. Parses args and runs the App.
|
||||
|
||||
mod app;
|
||||
mod history;
|
||||
mod types;
|
||||
mod ui;
|
||||
mod ws;
|
||||
|
||||
use std::env;
|
||||
use app::App;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
eprintln!("Usage: {} ws://HOST:PORT/ws", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
let url = args[1].clone();
|
||||
|
||||
let mut app = App::new();
|
||||
app.run(&url).await
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//! Types that mirror the agent's JSON schema.
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Disk {
|
||||
pub name: String,
|
||||
pub total: u64,
|
||||
pub available: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Network {
|
||||
// cumulative totals; client diffs to compute rates
|
||||
pub received: u64,
|
||||
pub transmitted: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: u32,
|
||||
pub name: String,
|
||||
pub cpu_usage: f32,
|
||||
pub mem_bytes: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Metrics {
|
||||
pub cpu_total: f32,
|
||||
pub cpu_per_core: Vec<f32>,
|
||||
pub mem_total: u64,
|
||||
pub mem_used: u64,
|
||||
pub swap_total: u64,
|
||||
pub swap_used: u64,
|
||||
pub process_count: usize,
|
||||
pub hostname: String,
|
||||
pub cpu_temp_c: Option<f32>,
|
||||
pub disks: Vec<Disk>,
|
||||
pub networks: Vec<Network>,
|
||||
pub top_processes: Vec<ProcessInfo>,
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! CPU average sparkline + per-core mini bars.
|
||||
|
||||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph, Sparkline},
|
||||
};
|
||||
use ratatui::style::Modifier;
|
||||
|
||||
use crate::history::PerCoreHistory;
|
||||
use crate::types::Metrics;
|
||||
|
||||
pub fn draw_cpu_avg_graph(
|
||||
f: &mut ratatui::Frame<'_>,
|
||||
area: Rect,
|
||||
hist: &std::collections::VecDeque<u64>,
|
||||
m: Option<&Metrics>,
|
||||
) {
|
||||
let title = if let Some(mm) = m { format!("CPU avg (now: {:>5.1}%)", mm.cpu_total) } else { "CPU avg".into() };
|
||||
let max_points = area.width.saturating_sub(2) as usize;
|
||||
let start = hist.len().saturating_sub(max_points);
|
||||
let data: Vec<u64> = hist.iter().skip(start).cloned().collect();
|
||||
let spark = Sparkline::default()
|
||||
.block(Block::default().borders(Borders::ALL).title(title))
|
||||
.data(&data)
|
||||
.max(100)
|
||||
.style(Style::default().fg(Color::Cyan));
|
||||
f.render_widget(spark, area);
|
||||
}
|
||||
|
||||
pub fn draw_per_core_bars(
|
||||
f: &mut ratatui::Frame<'_>,
|
||||
area: Rect,
|
||||
m: Option<&Metrics>,
|
||||
per_core_hist: &PerCoreHistory,
|
||||
) {
|
||||
f.render_widget(Block::default().borders(Borders::ALL).title("Per-core"), area);
|
||||
let Some(mm) = m else { return; };
|
||||
|
||||
let inner = Rect { x: area.x + 1, y: area.y + 1, width: area.width.saturating_sub(2), height: area.height.saturating_sub(2) };
|
||||
if inner.height == 0 { return; }
|
||||
|
||||
let rows = inner.height as usize;
|
||||
let show_n = rows.min(mm.cpu_per_core.len());
|
||||
let constraints: Vec<Constraint> = (0..show_n).map(|_| Constraint::Length(1)).collect();
|
||||
let vchunks = Layout::default().direction(Direction::Vertical).constraints(constraints).split(inner);
|
||||
|
||||
for i in 0..show_n {
|
||||
let rect = vchunks[i];
|
||||
let hchunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Min(6), Constraint::Length(12)])
|
||||
.split(rect);
|
||||
|
||||
let curr = mm.cpu_per_core[i].clamp(0.0, 100.0);
|
||||
let older = per_core_hist.deques.get(i)
|
||||
.and_then(|d| d.iter().rev().nth(20).copied())
|
||||
.map(|v| v as f32)
|
||||
.unwrap_or(curr);
|
||||
let trend = if curr > older + 0.2 { "↑" }
|
||||
else if curr + 0.2 < older { "↓" }
|
||||
else { "╌" };
|
||||
|
||||
let fg = match curr {
|
||||
x if x < 25.0 => Color::Green,
|
||||
x if x < 60.0 => Color::Yellow,
|
||||
_ => Color::Red,
|
||||
};
|
||||
|
||||
let hist: Vec<u64> = per_core_hist
|
||||
.deques
|
||||
.get(i)
|
||||
.map(|d| {
|
||||
let max_points = hchunks[0].width as usize;
|
||||
let start = d.len().saturating_sub(max_points);
|
||||
d.iter().skip(start).map(|&v| v as u64).collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let spark = Sparkline::default()
|
||||
.data(&hist)
|
||||
.max(100)
|
||||
.style(Style::default().fg(fg));
|
||||
f.render_widget(spark, hchunks[0]);
|
||||
|
||||
let label = format!("cpu{:<2}{}{:>5.1}%", i, trend, curr);
|
||||
let line = Line::from(Span::styled(label, Style::default().fg(fg).add_modifier(Modifier::BOLD)));
|
||||
f.render_widget(Paragraph::new(line).right_aligned(), hchunks[1]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//! Disk cards with per-device gauge and title line.
|
||||
|
||||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::Style,
|
||||
widgets::{Block, Borders, Gauge},
|
||||
};
|
||||
use crate::types::Metrics;
|
||||
use crate::ui::util::{human, truncate_middle, disk_icon};
|
||||
|
||||
pub fn draw_disks(f: &mut ratatui::Frame<'_>, area: Rect, m: Option<&Metrics>) {
|
||||
f.render_widget(Block::default().borders(Borders::ALL).title("Disks"), area);
|
||||
let Some(mm) = m else { return; };
|
||||
|
||||
let inner = Rect {
|
||||
x: area.x + 1,
|
||||
y: area.y + 1,
|
||||
width: area.width.saturating_sub(2),
|
||||
height: area.height.saturating_sub(2),
|
||||
};
|
||||
if inner.height < 3 { return; }
|
||||
|
||||
let per_disk_h = 3u16;
|
||||
let max_cards = (inner.height / per_disk_h).min(mm.disks.len() as u16) as usize;
|
||||
|
||||
let constraints: Vec<Constraint> = (0..max_cards).map(|_| Constraint::Length(per_disk_h)).collect();
|
||||
let rows = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(constraints)
|
||||
.split(inner);
|
||||
|
||||
for (i, slot) in rows.iter().enumerate() {
|
||||
let d = &mm.disks[i];
|
||||
let used = d.total.saturating_sub(d.available);
|
||||
let ratio = if d.total > 0 { used as f64 / d.total as f64 } else { 0.0 };
|
||||
let pct = (ratio * 100.0).round() as u16;
|
||||
|
||||
let color = if pct < 70 { ratatui::style::Color::Green } else if pct < 90 { ratatui::style::Color::Yellow } else { ratatui::style::Color::Red };
|
||||
|
||||
let title = format!(
|
||||
"{} {} {} / {} ({}%)",
|
||||
disk_icon(&d.name),
|
||||
truncate_middle(&d.name, (slot.width.saturating_sub(6)) as usize / 2),
|
||||
human(used),
|
||||
human(d.total),
|
||||
pct
|
||||
);
|
||||
|
||||
let card = Block::default().borders(Borders::ALL).title(title);
|
||||
f.render_widget(card, *slot);
|
||||
|
||||
let inner_card = Rect {
|
||||
x: slot.x + 1,
|
||||
y: slot.y + 1,
|
||||
width: slot.width.saturating_sub(2),
|
||||
height: slot.height.saturating_sub(2),
|
||||
};
|
||||
if inner_card.height == 0 { continue; }
|
||||
|
||||
let gauge_rect = Rect {
|
||||
x: inner_card.x,
|
||||
y: inner_card.y + inner_card.height / 2,
|
||||
width: inner_card.width,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
let g = Gauge::default()
|
||||
.percent(pct)
|
||||
.gauge_style(Style::default().fg(color));
|
||||
|
||||
f.render_widget(g, gauge_rect);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//! Top header with hostname and CPU temperature indicator.
|
||||
|
||||
use ratatui::{
|
||||
layout::Rect,
|
||||
widgets::{Block, Borders},
|
||||
};
|
||||
use crate::types::Metrics;
|
||||
|
||||
pub fn draw_header(f: &mut ratatui::Frame<'_>, area: Rect, m: Option<&Metrics>) {
|
||||
let title = if let Some(mm) = m {
|
||||
let temp = mm.cpu_temp_c.map(|t| {
|
||||
let icon = if t < 50.0 { "😎" } else if t < 85.0 { "⚠️" } else { "🔥" };
|
||||
format!("CPU Temp: {:.1}°C {}", t, icon)
|
||||
}).unwrap_or_else(|| "CPU Temp: N/A".into());
|
||||
format!("socktop — host: {} | {} (press 'q' to quit)", mm.hostname, temp)
|
||||
} else {
|
||||
"socktop — connecting... (press 'q' to quit)".into()
|
||||
};
|
||||
f.render_widget(Block::default().title(title).borders(Borders::BOTTOM), area);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//! Memory gauge.
|
||||
|
||||
use ratatui::{
|
||||
layout::Rect,
|
||||
style::{Color, Style},
|
||||
widgets::{Block, Borders, Gauge},
|
||||
};
|
||||
use crate::types::Metrics;
|
||||
use crate::ui::util::human;
|
||||
|
||||
pub fn draw_mem(f: &mut ratatui::Frame<'_>, area: Rect, m: Option<&Metrics>) {
|
||||
let (used, total, pct) = if let Some(mm) = m {
|
||||
let pct = if mm.mem_total > 0 { (mm.mem_used as f64 / mm.mem_total as f64 * 100.0) as u16 } else { 0 };
|
||||
(mm.mem_used, mm.mem_total, pct)
|
||||
} else { (0, 0, 0) };
|
||||
|
||||
let g = Gauge::default()
|
||||
.block(Block::default().borders(Borders::ALL).title("Memory"))
|
||||
.gauge_style(Style::default().fg(Color::Magenta))
|
||||
.percent(pct)
|
||||
.label(format!("{} / {}", human(used), human(total)));
|
||||
f.render_widget(g, area);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//! UI module root: exposes drawing functions for individual panels.
|
||||
|
||||
pub mod header;
|
||||
pub mod cpu;
|
||||
pub mod mem;
|
||||
pub mod swap;
|
||||
pub mod disks;
|
||||
pub mod net;
|
||||
pub mod processes;
|
||||
pub mod util;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user