Compare commits

..

41 Commits

Author SHA1 Message Date
1b88c80669 chore: update tasks 2025-05-17 15:16:11 +00:00
af579d1a7f chore: remove access and authorized_keys files from project 2025-05-17 15:15:51 +00:00
e6fff0c044 feat: add validate_command_access.sh with its tests 2025-05-17 15:12:18 +00:00
ba052d78d4 feat: add gitops ability to update authorized_keys 2025-05-17 15:10:44 +00:00
bc1cbfc772 chore: change repo for validate_command and gitconfig to workspaces 2025-05-17 15:10:08 +00:00
f8b38996df feat: add package go-yq to build image 2025-05-17 14:43:42 +00:00
d488a87dd4 feat: upgrade gitops ability to fetch files from different repos 2025-05-17 14:42:06 +00:00
3e361cd03c feat: separate access validation logic for gitops commands into a separate file 2025-05-17 14:38:56 +00:00
fd0c07e954 feat: remove buildah scripts and adapt project to use Containerfile 2025-05-17 13:21:36 +00:00
d3f5e93ad8 feat: add containerfile for our builds 2025-05-17 13:12:00 +00:00
c9460b8ebc chore: update command access for pallav 2025-05-17 11:37:50 +00:00
9629c3253e fix: validate commands, improve remove container logic, standardize logs in gitops router 2025-05-17 11:37:06 +00:00
6e11d19510 feat: update authorized_keys file 2025-05-17 10:04:00 +00:00
a4bfe5a5c0 feat: use %h instead of hard coded paths in authorized_keys 2025-05-17 09:44:31 +00:00
54a42ad4d5 fix: use $HOME variable in place of hard coded path values 2025-05-17 09:39:07 +00:00
21eee8c3ec feat: remove access to host podman socket 2025-05-17 09:35:48 +00:00
ff72c95012 style: beatify and optimize ssh router with chatgpt 2025-05-17 09:21:48 +00:00
3576bf93c2 style: beautify and optimize gitops router with chatgpt 2025-05-17 09:15:50 +00:00
d7c7686a9e feat: generate gitconfig on the fly before creating container 2025-05-17 09:04:26 +00:00
a179a3ad23 chore: add task to copy gitconfig.template 2025-05-17 09:02:58 +00:00
56744155cb feat: add gitops function to update gitconfig template 2025-05-17 08:59:58 +00:00
eba420cc81 chore: adapt tasks.json for gitops workflow 2025-05-17 08:18:11 +00:00
a9adb834e5 feat: move image cleanup function inside gitops router 2025-05-17 08:08:15 +00:00
f422da8d9e chore: add project local settings for lazyvim 2025-05-17 07:23:20 +00:00
079979292d fix: correct geturl spelling 2025-05-17 07:22:02 +00:00
14a96035b6 feat: add gitops function to copy home.tar.gz 2025-05-17 07:13:49 +00:00
1fc4153048 chore: start tracking home.tar.gz 2025-05-17 07:01:19 +00:00
dceda5fb53 feat: track home.tar.gz using Git LFS 2025-05-17 06:57:03 +00:00
b67af4b482 feat: add gitops function to remove podman containers 2025-05-17 06:34:26 +00:00
61902a8b5b feat: remove ability to copy directly using scp/sftp 2025-05-17 06:33:36 +00:00
5181a3c194 Revert "fix: avoid logging to stdout before running scp to copy tarball"
This reverts commit f8a09a135b.
2025-05-17 06:28:26 +00:00
0e2f89bde4 feat: add git lfs package 2025-05-17 06:11:42 +00:00
f09654160a fix: use evec instead of eval for running scp 2025-05-17 02:59:32 +00:00
f8a09a135b fix: avoid logging to stdout before running scp to copy tarball 2025-05-17 02:49:14 +00:00
80a3878295 fix: check for scp early to avoid shell ouput 2025-05-17 02:47:30 +00:00
54e2ec2374 feat: add gitops router script 2025-05-17 01:57:50 +00:00
156b47aded feat: use locally built image for starting a pod 2025-05-16 23:35:00 +00:00
422a962a85 feat: change owner to devuser for .ssh/*, .config and .config/fish 2025-05-16 23:31:41 +00:00
991478a0b0 feat: split images into 2 - base and workspace 2025-05-16 23:28:39 +00:00
8812bb4528 feat(build): add uid and guid to files in tarball 2025-05-16 23:03:15 +00:00
a45494c949 chore: untrack lazy-lock.json 2025-05-16 20:45:43 +00:00
17 changed files with 626 additions and 290 deletions

View File

@ -1,12 +0,0 @@
#!/bin/bash
# Get list of image IDs with <none> tag (dangling images)
dangling_images=$(podman images -f "dangling=true" -q)
if [ -z "$dangling_images" ]; then
echo "✅ No dangling images to remove."
else
echo "⚠️ Removing dangling images..."
echo "$dangling_images" | xargs podman rmi -f
echo "🧹 Done!"
fi

View File

@ -14,5 +14,5 @@ replace_home() {
find .config -type d -exec chmod g+x {} + find .config -type d -exec chmod g+x {} +
replace_home "$PWD" "/home/devuser" replace_home "$PWD" "/home/devuser"
tar -czf home.tar.gz --owner root --group secproc --xform "s,$PWD,/home/devuser," .config .local .ssh start.sh tar -czf home.tar.gz --owner root:0 --group secproc:1002 --xform "s,$PWD,/home/devuser," .config .local .ssh start.sh
replace_home "/home/devuser" "$PWD" replace_home "/home/devuser" "$PWD"

9
.bin/gitops Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
ssh -F /dev/null \
-o HostName=10.88.0.1 \
-o Port=22 \
-o User=infilytics \
-o IdentityFile=~/.ssh/id_ed25519 \
-o ProxyCommand=none \
gitops -- "$@"

View File

@ -1,64 +0,0 @@
{
"LazyVim": { "branch": "main", "commit": "25abbf546d564dc484cf903804661ba12de45507" },
"LuaSnip": { "branch": "master", "commit": "c1851d5c519611dfc451b6582961b2602e0af89b" },
"SchemaStore.nvim": { "branch": "main", "commit": "ba7bad63cb96dae5a82e48310beada18e8eeafe5" },
"blink.cmp": { "branch": "main", "commit": "022521a8910a5543b0251b21c9e1a1e989745796" },
"blink.compat": { "branch": "main", "commit": "2ed6d9a28b07fa6f3bface818470605f8896408c" },
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
"catppuccin": { "branch": "main", "commit": "1bf070129c0b6f77cc23f6a2212dcdc868308c52" },
"codeium.nvim": { "branch": "main", "commit": "821b570b526dbb05b57aa4ded578b709a704a38a" },
"conform.nvim": { "branch": "master", "commit": "2b2b30260203af3b93a7470ac6c8457ddd6e32d9" },
"dial.nvim": { "branch": "master", "commit": "2c7e2750372918f072a20f3cf754d845e143d7c9" },
"diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
"flash.nvim": { "branch": "main", "commit": "3c942666f115e2811e959eabbdd361a025db8b63" },
"friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" },
"gitsigns.nvim": { "branch": "main", "commit": "e399f9748d7cfd8859747c8d6c4e9c8b4d50a1bd" },
"grug-far.nvim": { "branch": "main", "commit": "176ba4c42924f4d84ee7d19c9f0081c538f84a88" },
"inc-rename.nvim": { "branch": "main", "commit": "2eaff20526ff6101337b84f4b0d238c11f47d7f4" },
"lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" },
"lazydev.nvim": { "branch": "main", "commit": "2367a6c0a01eb9edb0464731cc0fb61ed9ab9d2c" },
"lualine.nvim": { "branch": "master", "commit": "15884cee63a8c205334ab13ab1c891cd4d27101a" },
"markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "1a31f824b9cd5bc6f342fc29e9a53b60d74af245" },
"mason-nvim-dap.nvim": { "branch": "main", "commit": "4c2cdc69d69fe00c15ae8648f7e954d99e5de3ea" },
"mason.nvim": { "branch": "main", "commit": "fc98833b6da5de5a9c5b1446ac541577059555be" },
"mini.ai": { "branch": "main", "commit": "e139eb1101beb0250fea322f8c07a42f0f175688" },
"mini.files": { "branch": "main", "commit": "49c855977e9f4821d1ed8179ed44fe098b93ea2a" },
"mini.hipatterns": { "branch": "main", "commit": "e5083df391171dc9d8172645606f8496d9443374" },
"mini.icons": { "branch": "main", "commit": "397ed3807e96b59709ef3292f0a3e253d5c1dc0a" },
"mini.pairs": { "branch": "main", "commit": "69864a2efb36c030877421634487fd90db1e4298" },
"mini.surround": { "branch": "main", "commit": "5aab42fcdcf31fa010f012771eda5631c077840a" },
"neogen": { "branch": "main", "commit": "d7f9461727751fb07f82011051338a9aba07581d" },
"neotest": { "branch": "master", "commit": "862afb2a2219d9ca565f67416fb7003cc0f22c4f" },
"neotest-python": { "branch": "master", "commit": "a2861ab3c9a0bf75a56b11835c2bfc8270f5be7e" },
"noice.nvim": { "branch": "main", "commit": "0427460c2d7f673ad60eb02b35f5e9926cf67c59" },
"nui.nvim": { "branch": "main", "commit": "f535005e6ad1016383f24e39559833759453564e" },
"nvim-dap": { "branch": "master", "commit": "8df427aeba0a06c6577dc3ab82de3076964e3b8d" },
"nvim-dap-python": { "branch": "master", "commit": "261ce649d05bc455a29f9636dc03f8cdaa7e0e2c" },
"nvim-dap-ui": { "branch": "master", "commit": "73a26abf4941aa27da59820fd6b028ebcdbcf932" },
"nvim-dap-virtual-text": { "branch": "master", "commit": "df66808cd78b5a97576bbaeee95ed5ca385a9750" },
"nvim-jdtls": { "branch": "master", "commit": "c23f200fee469a415c77265ca55b496feb646992" },
"nvim-lint": { "branch": "master", "commit": "fdb04e9285edefbe25a02a31a35e8fbb10fe054d" },
"nvim-lspconfig": { "branch": "master", "commit": "ac1dfbe3b60e5e23a2cff90e3bd6a3bc88031a57" },
"nvim-metals": { "branch": "main", "commit": "f9cc5e7f7bc129b8056f1e5aef7a91c9b5b83664" },
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
"nvim-treesitter": { "branch": "master", "commit": "066fd6505377e3fd4aa219e61ce94c2b8bdb0b79" },
"nvim-treesitter-textobjects": { "branch": "master", "commit": "b0debd5c424969b4baeabdc8f54db3036c691732" },
"nvim-ts-autotag": { "branch": "main", "commit": "a1d526af391f6aebb25a8795cbc05351ed3620b5" },
"one-small-step-for-vimkind": { "branch": "main", "commit": "ba909c68fed65e268df8a4684bafef4ec889c8bc" },
"overseer.nvim": { "branch": "master", "commit": "72c68aab0358c92f451168b704c411c4a3e3410e" },
"persistence.nvim": { "branch": "main", "commit": "166a79a55bfa7a4db3e26fc031b4d92af71d0b51" },
"plenary.nvim": { "branch": "master", "commit": "857c5ac632080dba10aae49dba902ce3abf91b35" },
"render-markdown.nvim": { "branch": "main", "commit": "a1b0988f5ab26698afb56b9c2f0525a4de1195c1" },
"snacks.nvim": { "branch": "main", "commit": "bc0630e43be5699bb94dadc302c0d21615421d93" },
"todo-comments.nvim": { "branch": "main", "commit": "304a8d204ee787d2544d8bc23cd38d2f929e7cc5" },
"tokyonight.nvim": { "branch": "main", "commit": "057ef5d260c1931f1dffd0f052c685dcd14100a3" },
"trouble.nvim": { "branch": "main", "commit": "85bedb7eb7fa331a2ccbecb9202d8abba64d37b3" },
"ts-comments.nvim": { "branch": "main", "commit": "1bd9d0ba1d8b336c3db50692ffd0955fe1bb9f0c" },
"vim-dadbod": { "branch": "master", "commit": "e95afed23712f969f83b4857a24cf9d59114c2e6" },
"vim-dadbod-completion": { "branch": "master", "commit": "a8dac0b3cf6132c80dc9b18bef36d4cf7a9e1fe6" },
"vim-dadbod-ui": { "branch": "master", "commit": "460432301a5cb280ea265ddfa15c9f3dcd1d26b7" },
"vim-illuminate": { "branch": "master", "commit": "fbc16dee336d8cc0d3d2382ea4a53f4a29725abf" },
"which-key.nvim": { "branch": "main", "commit": "370ec46f710e058c9c1646273e6b225acf47cbed" },
"yanky.nvim": { "branch": "main", "commit": "04775cc6e10ef038c397c407bc17f00a2f52b378" }
}

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
home.tar.gz filter=lfs diff=lfs merge=lfs -text

2
.gitignore vendored
View File

@ -4,4 +4,4 @@ logs
.state .state
.config/fish .config/fish
.npm .npm
home.tar.gz .config/nvim/lazy-lock.json

24
.lazy.lua Normal file
View File

@ -0,0 +1,24 @@
return {
"folke/snacks.nvim",
opts = {
-- show hidden files in snacks.explorer
picker = {
sources = {
explorer = {
-- show hidden files like .env
hidden = true,
-- show files ignored by git like node_modules
ignored = false,
exclude = { ".git" },
},
files = {
-- show hidden files like .env
hidden = true,
-- show files ignored by git like node_modules
ignored = false,
exclude = { ".npm", ".git" },
},
},
},
},
}

99
.vscode/tasks.json vendored
View File

@ -2,70 +2,129 @@
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "Build workspace image", "label": "GitOps(Build): base image",
"type": "shell", "type": "shell",
"command": "${workspaceFolder}/build-workspace.sh", "command": ".bin/gitops build base",
"group": "build",
"problemMatcher": [],
"detail": "build base image using buildah"
},
{
"label": "GitOps(Build): workspace image",
"type": "shell",
"command": ".bin/gitops build workspace",
"group": "build", "group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "build podman image using buildah" "detail": "build podman image using buildah"
}, },
{ {
"label": "Clean dangling images", "label": "GitOps(Build): all images",
"type": "shell", "type": "shell",
"command": "${workspaceFolder}/.bin/clean_dangling_images.sh", "command": ".bin/gitops build all",
"group": "build",
"problemMatcher": [],
"detail": "build podman image using buildah"
},
{
"label": "GitOps: Clean dangling images",
"type": "shell",
"command": ".bin/gitops clean",
"problemMatcher": [], "problemMatcher": [],
"detail": "Clean podman images" "detail": "Clean podman images"
}, },
{ {
"label": "Tag image", "label": "GitOps(Update): Containerfile",
"type": "shell", "type": "shell",
"command": "podman tag localhost/analytics-backend-workspace:latest localhost:5100/analytics-backend-workspace:latest", "command": ".bin/gitops update containerfile",
"group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "Tag podman image to localhost 5100" "detail": "Copy Containerfile to $HOME/"
}, },
{ {
"label": "Push image", "label": "GitOps(Update): ssh_router.sh",
"type": "shell", "type": "shell",
"command": "podman push --tls-verify=false localhost:5100/analytics-backend-workspace:latest", "command": ".bin/gitops update ssh_router",
"group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "Push podman image to localhost 5100" "detail": "Copy ssh_router.sh to $HOME/.local/bin/"
}, },
{ {
"label": "Copy ssh_router.sh", "label": "GitOps(Update): gitops_router.sh",
"type": "shell", "type": "shell",
"command": "sudo cp ssh_router.sh /home/infilytics/ && sudo chown -R infilytics:infilytics /home/infilytics/ssh_router.sh", "command": ".bin/gitops update gitops_router",
"group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "Copy ssh_router.sh to /home/infilytics" "detail": "Copy gitops_router.sh to $HOME/.local/bin"
}, },
{ {
"label": "Copy access.yml", "label": "GitOps(Update): home.tar.gz",
"type": "shell", "type": "shell",
"command": "sudo cp access.yml /home/infilytics/ && sudo chown -R infilytics:infilytics /home/infilytics/access.yml", "command": ".bin/gitops update home_tar",
"group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "Copy access.yml to /home/infilytics" "detail": "Copy home.tar.gz to $HOME/"
},
{
"label": "GitOps(Update): gitconfig.template",
"type": "shell",
"command": ".bin/gitops update gitconfig",
"group": "build",
"problemMatcher": [],
"detail": "Copy gitconfig.template to $HOME/"
},
{
"label": "GitOps(Update): validate_command_access.sh",
"type": "shell",
"command": ".bin/gitops update validate_command",
"group": "build",
"problemMatcher": [],
"detail": "Copy validate_command_access.sh to $HOME/.local/bin"
}, },
{ {
"label": "Create home tarball", "label": "Create home tarball",
"type": "shell", "type": "shell",
"command": "${workspaceFolder}/.bin/create-home-tarball.sh", "command": "${workspaceFolder}/.bin/create-home-tarball.sh",
"group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "create home.tar.gz from .config .local .ssh start.sh" "detail": "create home.tar.gz from .config .local .ssh start.sh"
}, },
{ {
"label": "Start a tmux test session", "label": "Test: start tmux session",
"type": "shell", "type": "shell",
"group": "test",
"command": "${workspaceFolder}/.bin/test-tmux.sh", "command": "${workspaceFolder}/.bin/test-tmux.sh",
"group": "test",
"problemMatcher": [], "problemMatcher": [],
"detail": "run tmux with project room as home" "detail": "run tmux with project room as home"
}, },
{ {
"label": "Delete .gitignore files", "label": "GitOps: Show image status",
"type": "shell",
"command": ".bin/gitops status",
"problemMatcher": [],
"detail": "run podman images on remote"
},
{
"label": "GitOps: Remove workspace container",
"type": "shell",
"command": ".bin/gitops remove ${input:container} -f",
"problemMatcher": [],
"detail": "run podman rm $args on remote"
},
{
"label": "Cleanup worktree",
"type": "shell", "type": "shell",
"command": "git clean -Xfd", "command": "git clean -Xfd",
"problemMatcher": [], "problemMatcher": [],
"detail": "delete all untracked files listed in .gitignore" "detail": "delete all untracked files listed in .gitignore"
} }
], ],
"inputs": [] "inputs": [
{
"id": "container",
"type": "pickString",
"description": "Pick a container",
"options": ["pallav", "palak", "param", "darshan"],
"default": "pallav"
}
]
} }

60
Containerfile Normal file
View File

@ -0,0 +1,60 @@
# ───────────────────
# Stage 1: Base Image
# ───────────────────
FROM archlinux:base-devel-20250511.0.348143 as base
ARG DEV_USER=devuser
ARG DEV_UID=1001
ARG DEV_GID=1001
# Install all necessary packages and clean up cache
RUN pacman -Sy --noconfirm && \
pacman -S --noconfirm --needed \
base-devel neovim git git-lfs fish tmux go-yq \
nodejs python podman fzf fd ripgrep jdk-openjdk fisher yazi less \
lazygit luarocks python-pynvim npm bash-completion tree-sitter-cli kitty-terminfo \
lua51 openssh && \
pacman -Scc --noconfirm && \
rm -rf /var/cache/pacman/pkg/*
# Create user/groups as per your script, with -l to avoid system user quirks
RUN groupadd -g $DEV_GID $DEV_USER && \
groupadd -g 1002 secproc && \
useradd -l -ms /bin/fish -G secproc -u $DEV_UID -g $DEV_GID $DEV_USER
# ────────────────────────
# Stage 2: Workspace Image
# ────────────────────────
FROM base as workspace
ARG DEV_USER=devuser
ARG DEV_UID=1001
ARG DEV_GID=1001
ARG DEV_HOME=/home/$DEV_USER
# Use ADD for extracting archives
ADD home.tar.gz $DEV_HOME
# Prepare .ssh and known_hosts, and fix permissions only if dirs exist
RUN mkdir -p $DEV_HOME/.ssh && \
ssh-keyscan -p 2222 10.88.0.1 >> $DEV_HOME/.ssh/known_hosts && \
ssh-keyscan -p 22 github.com >> $DEV_HOME/.ssh/known_hosts && \
for d in $DEV_HOME/.local \
$DEV_HOME/.config/fish/completions \
$DEV_HOME/.config/fish/functions \
$DEV_HOME/.config/fish/fish_variables \
$DEV_HOME/.ssh; do \
if [ -e "$d" ]; then chown -R $DEV_USER:$DEV_USER "$d"; fi; \
done && \
for d in $DEV_HOME/.local \
$DEV_HOME/.config \
$DEV_HOME/.config/fish \
$DEV_HOME/.config/tmux; do \
if [ -e "$d" ]; then chown $DEV_USER:$DEV_USER "$d"; fi; \
done
WORKDIR /app
ENV CONTAINER_HOST=unix:///run/podman/podman.sock
USER $DEV_USER
CMD ["/home/devuser/start.sh"]

View File

@ -1,30 +0,0 @@
pallav:
name: Pallav Vasa
email: pallav@infilytics.in
rw:
- darshan
- param
- palak
darshan:
name: Darshan Parmar
email: darshan@infilytics.in
rw:
- param
ro:
- pallav
param:
name: Param Makawana
email: param@infilytics.in
ro:
- pallav
- darshan
palak:
name: Palak Vasa
email: pakak@infilytics.in
ro:
- pallav
- param
- darshan

View File

@ -1 +0,0 @@
command="/home/infilytics/ssh_router.sh pallav",no-port-forwarding,no-agent-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK0il/OJiXygyPWYBt05+OQYjJPxgGuP3kP9hLsD/C7x phoenix@sphinx

View File

@ -1,42 +0,0 @@
#!/bin/bash
set -euo pipefail
IMG_NAME="analytics-backend-workspace"
DEV_USER=devuser
DEV_UID=1001
DEV_GID=1001
DEV_HOME=/home/$DEV_USER
ctr=$(buildah from archlinux)
buildah run "$ctr" -- bash -c "\
pacman -Sy --noconfirm && pacman -S --noconfirm --needed base-devel neovim git fish tmux \
nodejs python podman fzf fd ripgrep jdk-openjdk fisher yazi less buildah \
lazygit luarocks python-pynvim npm bash-completion tree-sitter-cli kitty-terminfo \
lua51 openssh && pacman -Scc --noconfirm && groupadd secproc && groupadd -g $DEV_GID $DEV_USER && \
useradd -ms /bin/fish -G secproc -u $DEV_UID -g $DEV_GID $DEV_USER
"
buildah add "$ctr" home.tar.gz $DEV_HOME
# shellcheck disable=SC2016
buildah run "$ctr" -- fish -c '
set -gx HOME '"$DEV_HOME"';
ssh-keyscan -p 2222 10.88.0.1 >> $HOME/.ssh/known_hosts;
ssh-keyscan -p 22 github.com >> $HOME/.ssh/known_hosts;
chown -R '"$DEV_USER"':'"$DEV_USER"' $HOME/.local $HOME/.config/fish/completions \
$HOME/.config/fish/functions $HOME/.config/fish/fish_variables;
chown '"$DEV_USER"':'"$DEV_USER"' $HOME/.config/tmux;
'
buildah config \
--user $DEV_USER \
--workingdir /app \
--env CONTAINER_HOST=unix:///run/podman/podman.sock \
--cmd "[\"$DEV_HOME/start.sh\"]" \
"$ctr"
buildah commit "$ctr" $IMG_NAME
echo "$IMG_NAME built."

178
gitops_router.sh Normal file
View File

@ -0,0 +1,178 @@
#!/usr/bin/env bash
set -euo pipefail
PERSON="${1:?Missing PERSON argument}"
HOST="alps:3222"
PROTOCOL="http"
REPO=("babbarc/workspaces" "babbarc/workspaces-sec-alps-infilytics")
BRANCH="master"
LOG_FILE="/tmp/.gitops-router-${PERSON}.log"
# ─────────────────────────────────────────────
# ANSI color codes
readonly C_RESET='\033[0m'
readonly C_INFO='\033[1;34m' # bold blue
readonly C_WARN='\033[1;33m' # bold yellow
readonly C_ERROR='\033[1;31m' # bold red
# ─────────────────────────────────────────────
# log <level> <message...> with emojis
log() {
local lvl="${1^^}"
shift
local icon color
case "$lvl" in
INFO) icon="" color="$C_INFO" ;;
WARN) icon="⚠️" color="$C_WARN" ;;
ERROR) icon="❌" color="$C_ERROR" ;;
*) icon="🔹" color="$C_RESET" ;;
esac
local ts
ts="$(date '+%Y-%m-%d %H:%M:%S')"
printf '%b%s [%s] [%s] %s%b\n' \
"$color" "$icon" "$ts" "$lvl" "$*" "$C_RESET" |
tee -a "$LOG_FILE"
}
# ─────────────────────────────────────────────
# Build the raw URL for fetching files
geturl() {
local repo="$1" type="$2" file="$3"
printf '%s://%s/%s/%s/branch/%s/%s\n' \
"$PROTOCOL" "$HOST" "${REPO[$repo]}" "$type" "$BRANCH" "$file"
}
# ─────────────────────────────────────────────
# Run a local script
run() {
local script="$1"
"$HOME/.local/bin/$script"
}
# ─────────────────────────────────────────────
# Download & install an artifact
# update <repo> <file> <target-dir> <mode> [<type>]
update() {
local repo="$1" file="$2" dir="$3" mode="$4" type="${5:-raw}"
local url out
out="$HOME/$dir/$(basename "$file")"
url="$(geturl "$repo" "$type" "$file")"
[[ -f "$out" ]] && chmod 700 "$out"
if curl -fsSL "$url" -o "$out"; then
log INFO "Downloaded $url$out"
chmod "$mode" "$out"
else
log ERROR "Failed to download $url"
return 1
fi
}
# ─────────────────────────────────────────────
# Clean up dangling podman images
clean_images() {
local dangling
dangling="$(podman images -f dangling=true -q)"
if [[ -z "$dangling" ]]; then
log INFO "No dangling images to remove."
else
log WARN "Removing dangling images..."
echo "$dangling" | xargs podman rmi
log INFO "Dangling images removed."
fi
}
# ─────────────────────────────────────────────
# Remove host podman containers
remove_containers() {
local tokens=("$@")
local flags=() patterns=() containers=()
local valid='^[A-Za-z0-9._-]+$'
# allow unmatched globs to disappear
shopt -s nullglob
# separate flags (-f, etc.) from name patterns
for tok in "${tokens[@]}"; do
if [[ "$tok" == -* ]]; then
flags+=("$tok")
else
patterns+=("$tok")
fi
done
# validate & expand each pattern
for pat in "${patterns[@]}"; do
if [[ ! "$pat" =~ $valid ]]; then
log ERROR "Invalid container name: '$pat'"
shopt -u nullglob
return 1
fi
containers+=("$pat")
done
shopt -u nullglob
if ((${#containers[@]} == 0)); then
log WARN "No containers matched: ${patterns[*]}"
return 0
fi
# pass flags *then* containers to podman rm
podman rm "${flags[@]}" "${containers[@]}"
}
# ─────────────────────────────────────────────
# validate_command <workspace> <cmd> [<tok1> <tok2> …]
source "$HOME"/.local/bin/validate_command_access.sh
# ─────────────────────────────────────────────
# Entry & command parsing
if [[ -z "${SSH_ORIGINAL_COMMAND:-}" ]]; then
log ERROR "No SSH_ORIGINAL_COMMAND provided."
exit 1
fi
log INFO "SSH_ORIGINAL_COMMAND: $SSH_ORIGINAL_COMMAND"
read -ra parts <<<"$SSH_ORIGINAL_COMMAND"
cmd="${parts[0]}"
args=("${parts[@]:1}")
validate_command "$PERSON" "$cmd" "${args[@]}"
# ─────────────────────────────────────────────
# Dispatch
case "$cmd" in
build)
case "${args[0]}" in
base) podman build --target base -t analytics-backend-base . ;;
workspace) podman build --target base -t analytics-backend-base . ;;
all) podman build -t analytics-backend-workspace . ;;
*) log ERROR "build: invalid arg '${args[0]}'" ;;
esac
;;
update)
case "${args[0]}" in
containerfile) update 0 Containerfile . 500 ;;
access) update 1 access.yml . 400 ;;
authorized_keys) update 1 access.yml . 400 ;;
ssh_router) update 0 ssh_router.sh .local/bin 500 ;;
gitops_router) update 0 gitops_router.sh .local/bin 500 ;;
validate_command) update 0 validate_command_access.sh .local/bin 500 ;;
home_tar) update 0 home.tar.gz . 500 media ;;
gitconfig) update 0 gitconfig.template . 500 ;;
*) log ERROR "update: invalid arg '${args[0]}'" ;;
esac
;;
clean) clean_images ;;
status) podman images ;;
remove) remove_containers "${args[@]}" ;;
*)
log ERROR "Unknown command: '$cmd'"
exit 127
;;
esac

BIN
home.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,187 +1,208 @@
#!/bin/bash #!/usr/bin/env bash
set -euo pipefail
PERSON="$1" PERSON="${1:?Usage: $0 <person>}"
WORKSPACE="$SSH_ORIGINAL_COMMAND" WORKSPACE="${SSH_ORIGINAL_COMMAND:-}"
IMAGE="localhost:5100/analytics-backend-workspace:latest" IMAGE="localhost/analytics-backend-workspace:latest"
DEV_USER="devuser" DEV_USER="devuser"
XDG_RUNTIME_DIR="/run/user/$(id -u)" XDG_RUNTIME_DIR="/run/user/$(id -u)"
LOG_FILE="/tmp/.ssh-router-${PERSON}.log" LOG_FILE="/tmp/.ssh-router-${PERSON}.log"
# ─────────────────────────────────────────────
# ANSI colors & emojis
readonly C_RESET='\033[0m'
readonly C_INFO='\033[1;34m' # blue
readonly C_WARN='\033[1;33m' # yellow
readonly C_ERROR='\033[1;31m' # red
log() { log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >>"$LOG_FILE" local level="${1^^}"
shift
local icon color
case "$level" in
INFO) icon="" color="$C_INFO" ;;
WARN) icon="⚠️" color="$C_WARN" ;;
ERROR) icon="❌" color="$C_ERROR" ;;
*) icon="🔹" color="$C_RESET" ;;
esac
local ts
ts="$(date '+%Y-%m-%d %H:%M:%S')"
printf '%b%s [%s] %s%b\n' \
"$color" "$icon" "$ts" "[$level] $*" "$C_RESET" |
tee -a "$LOG_FILE"
} }
# ─────────────────────────────────────────────
# Check for interactive TTY
if [[ ! -t 0 ]]; then if [[ ! -t 0 ]]; then
log "No TTY allocatedrefusing to run tmux without an interactive terminal" log ERROR "No TTY allocatedrefusing to run without an interactive terminal"
echo "Error: No TTY. Use 'ssh -t'" >&2 echo "Error: No TTY. Use 'ssh -t'" >&2
exit 1 exit 1
fi fi
# log "🧩 IMAGE = '$IMAGE'" # ─────────────────────────────────────────────
# log "🧩 WORKSPACE = '$WORKSPACE'" # Default WORKSPACE if empty
# log "🧩 PERSON = '$PERSON'" if [[ -z "$WORKSPACE" ]]; then
# Fallbacks
if [[ -z "${WORKSPACE:-}" ]]; then
WORKSPACE="$PERSON" WORKSPACE="$PERSON"
log " Defaulted WORKSPACE to $WORKSPACE" log INFO "Defaulted WORKSPACE $WORKSPACE"
fi fi
TMUX_SESSION="${WORKSPACE}|analytics-backend"
TMUX_SESSION="$WORKSPACE|analytics-backend" # ─────────────────────────────────────────────
# Ensure Podman socket is up
# Start podman socket service if it's not running ensure_podman() {
if [[ ! -S "$XDG_RUNTIME_DIR/podman/podman.sock" ]]; then local sock="$XDG_RUNTIME_DIR/podman/podman.sock"
log "🔄 Starting Podman socket service for user $USER" if [[ ! -S "$sock" ]]; then
log INFO "Starting podman.socket for user $(id -un)"
systemctl --user start podman.socket || { systemctl --user start podman.socket || {
log "Failed to start podman.socket via systemd" log ERROR "Failed to start podman.socket"
exit 1 exit 1
} }
# Wait briefly for socket to appear
sleep 1 sleep 1
fi fi
[[ -S "$sock" ]] || {
if [[ ! -S "$XDG_RUNTIME_DIR/podman/podman.sock" ]]; then log ERROR "Podman socket still missing"
log "❌ Podman socket still missing after startup attempt"
exit 1 exit 1
fi }
}
ensure_podman
# Check if image exists locally # ─────────────────────────────────────────────
# Ensure IMAGE is present
ensure_image() {
if ! podman image exists "$IMAGE"; then if ! podman image exists "$IMAGE"; then
log "📦 Image $IMAGE not found locally. Pulling from registry..." log WARN "Image $IMAGE not found—pulling"
podman pull --tls-verify=false "$IMAGE" || {
# Attempt to pull the image from the local registry (insecure HTTP) log ERROR "Failed to pull $IMAGE"
if ! podman pull --tls-verify=false "$IMAGE"; then
log "❌ Failed to pull image from $IMAGE"
exit 1 exit 1
}
log INFO "Pulled $IMAGE"
fi fi
}
ensure_image
log "✅ Successfully pulled $IMAGE" # ─────────────────────────────────────────────
fi # Disallow file transfers
case "$SSH_ORIGINAL_COMMAND" in case "$SSH_ORIGINAL_COMMAND" in
*scp* | *sftp* | *rsync* | *tar*) *scp* | *sftp* | *rsync* | *tar*)
log "File transfers are disabled" log ERROR "File transfers are disabled"
exit 1 exit 1
;; ;;
esac esac
# Function to start the container if not running # ─────────────────────────────────────────────
# Generate per-user gitconfig
generate_gitconfig() {
local access="$HOME/access.yml"
local template="$HOME/gitconfig.template"
local userdir="$HOME/secrets/$PERSON"
local name email
name=$(yq -r ".\"$PERSON\".name" "$access" 2>/dev/null || echo)
email=$(yq -r ".\"$PERSON\".email" "$access" 2>/dev/null || echo)
if [[ -z "$name" || -z "$email" ]]; then
log ERROR "Missing name/email for '$PERSON' in $access"
exit 1
fi
mkdir -p "$userdir"
GIT_NAME="$name" GIT_EMAIL="$email" \
envsubst <"$template" >"$userdir/gitconfig"
log INFO ".gitconfig created → $userdir/gitconfig"
}
# ─────────────────────────────────────────────
# Start container if absent or stopped
start_container_if_needed() { start_container_if_needed() {
if ! podman container exists "$WORKSPACE"; then if ! podman container exists "$WORKSPACE"; then
log "🚀 Creating container $WORKSPACE..." log INFO "Creating container '$WORKSPACE'"
generate_gitconfig
podman run -dit \ podman run -dit \
--userns=keep-id \
--name "$WORKSPACE" \ --name "$WORKSPACE" \
--userns=keep-id \
--user "$DEV_USER" \ --user "$DEV_USER" \
--hostname "$WORKSPACE" \ --hostname "$WORKSPACE" \
--label auto-cleanup=true \ --label auto-cleanup=true \
-v "${XDG_RUNTIME_DIR}"/podman/podman.sock:/run/podman/podman.sock \ -v "$HOME/data/$WORKSPACE:/app:Z" \
-v /home/infilytics/data/"$WORKSPACE":/app \ -v "$HOME/secrets/$WORKSPACE/gitconfig:/home/$DEV_USER/.gitconfig:ro,Z" \
-v /home/infilytics/secrets/"$WORKSPACE"/gitconfig:/home/"$DEV_USER"/.gitconfig:ro \ -v "$HOME/secrets/$WORKSPACE/id_ed25519:/home/$DEV_USER/.ssh/id_ed25519:ro,Z" \
-v /home/infilytics/secrets/"$WORKSPACE"/id_ed25519:/home/"$DEV_USER"/.ssh/id_ed25519:ro \ -v "$HOME/secrets/$WORKSPACE/id_ed25519.pub:/home/$DEV_USER/.ssh/id_ed25519.pub:ro,Z" \
-v /home/infilytics/secrets/"$WORKSPACE"/id_ed25519.pub:/home/"$DEV_USER"/.ssh/id_ed25519.pub:ro \
--entrypoint "/home/$DEV_USER/start.sh" \ --entrypoint "/home/$DEV_USER/start.sh" \
"$IMAGE" "${TMUX_SESSION}" "$IMAGE" "$TMUX_SESSION"
elif ! podman inspect -f '{{.State.Running}}' "$WORKSPACE" | grep -q true; then elif ! podman inspect -f '{{.State.Running}}' "$WORKSPACE" | grep -q true; then
log "Starting existing container $WORKSPACE..." log INFO "Starting existing container '$WORKSPACE'"
podman start "$WORKSPACE" >/dev/null 2>&1 podman start "$WORKSPACE" >/dev/null
fi fi
sleep 1 sleep 1
} }
# After devuser exits... # ─────────────────────────────────────────────
# Detach logic: stop container when devuser has left
check_devuser_attached() { check_devuser_attached() {
# Get list of clients local clients
client_users=$(podman exec "$WORKSPACE" tmux list-clients -t "$TMUX_SESSION" -F "#{client_user}" 2>/dev/null) clients=$(podman exec "$WORKSPACE" tmux list-clients -t "$TMUX_SESSION" -F "#{client_user}" 2>/dev/null)
if grep -q "^${DEV_USER}\$" <<<"$clients"; then
if echo "$client_users" | grep -q "$DEV_USER"; then log INFO "devuser still attached—keeping container running"
log "💡 devuser still attached — container stays running"
return 0
else else
log "🏃 $PERSON has logged out — stopping container" log INFO "devuser detached—stopping container"
podman stop "$WORKSPACE" >/dev/null 2>&1 podman stop "$WORKSPACE" >/dev/null
return 1
fi fi
} }
# ─────────────────────────────────────────────
# Determine access mode (rw|ro) or exit
get_access_mode() { get_access_mode() {
local yaml_file="access.yml" local yaml="access.yml" user="$PERSON" ws="$WORKSPACE"
local workspace="$1" [[ ! "$ws" =~ ^[A-Za-z0-9._-]+$ ]] && {
local person="$2" log ERROR "Invalid workspace name"
if [[ ! "$workspace" =~ ^[a-zA-Z0-9._-]+$ ]]; then
log "❌ Invalid container name: $WORKSPACE"
exit 1
fi
# Special case: user accessing their own workspace
if [[ "$workspace" == "$person" ]]; then
echo "access=rw"
return 0
fi
# Check rw
if yq '.["'"$person"'"].rw // []' "$yaml_file" | grep -q "\b$workspace\b"; then
echo "access=rw"
return 0
fi
# Check ro
if yq '.["'"$person"'"].ro // []' "$yaml_file" | grep -q "\b$workspace\b"; then
echo "access=ro"
return 0
fi
# No access → exit with error
log "$person has no access to $workspace" >&2
exit 1 exit 1
} }
if [[ "$user" == "$ws" ]]; then
echo rw
elif yq -e '.["'"$user"'"].rw[]?' "$yaml" | grep -qx "$ws"; then
echo rw
elif yq -e '.["'"$user"'"].ro[]?' "$yaml" | grep -qx "$ws"; then
echo ro
else
log ERROR "$user has no access to $ws"
exit 1
fi
}
# === Main === MODE="$(get_access_mode)"
read -r access_line < <(get_access_mode "$WORKSPACE" "$PERSON") || exit 1
MODE="${access_line#access=}"
# ─────────────────────────────────────────────
# Main dispatch
case "$MODE" in case "$MODE" in
rw) rw)
start_container_if_needed start_container_if_needed
# Run tmux session inside the container # Ensure tmux session exists
if ! podman exec -it --user "$DEV_USER" "$WORKSPACE" tmux has-session -t "$TMUX_SESSION" >/dev/null 2>&1; then if ! podman exec -it --user "$DEV_USER" "$WORKSPACE" tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then
if ! podman exec -it -e EDITOR=nvim --user "$DEV_USER" "$WORKSPACE" tmux new-session -d -s "$TMUX_SESSION" >/dev/null 2>&1; then podman exec -it --user "$DEV_USER" "$WORKSPACE" \
log "❌ Could not create new tmux session. Please contact admin or try again later." tmux new-session -d -s "$TMUX_SESSION"
exit 1
fi
fi fi
log "$PERSON is working on $WORKSPACE's workspace" log INFO "$PERSON attaching to workspace '$WORKSPACE'"
if ! podman exec -it -e TERM="$TERM" --user "$DEV_USER" "$WORKSPACE" tmux attach -t "$TMUX_SESSION"; then podman exec -it -e TERM="$TERM" --user "$DEV_USER" "$WORKSPACE" \
log "❌ Could not attach to tmux session. Please contact admin or try again later." tmux attach -t "$TMUX_SESSION"
exit 1 log INFO "$PERSON detached from '$WORKSPACE'"
fi
log "$PERSON finished working on $WORKSPACE's worksapce"
check_devuser_attached check_devuser_attached
exit 0
;; ;;
ro) ro)
if (podman container exists "$WORKSPACE" && podman inspect -f '{{.State.Running}}' "$WORKSPACE" | grep -q true) >/dev/null 2>&1; then if podman inspect -f '{{.State.Running}}' "$WORKSPACE" 2>/dev/null | grep -q true; then
log "📜 $PERSON is viewing $WORKSPACE's workspace" log INFO "$PERSON viewing workspace '$WORKSPACE'"
if ! podman exec -it -e TERM="$TERM" --user "$DEV_USER" "$WORKSPACE" tmux attach -r -t "$TMUX_SESSION"; then podman exec -it -e TERM="$TERM" --user "$DEV_USER" "$WORKSPACE" \
log "❌ Could not attach to tmux session. Please contact admin or try again later." tmux attach -r -t "$TMUX_SESSION"
exit 1 log INFO "$PERSON stopped viewing '$WORKSPACE'"
fi
log "🏃 $PERSON stopped viewing $WORKSPACE's workspace"
exit 0
else else
log "Workspace for $WORKSPACE does not exist." log ERROR "Workspace '$WORKSPACE' is not running"
exit 1 exit 1
fi fi
;; ;;
*) *)
log "❌ Invalid access mode: $MODE" log ERROR "Unknown access mode: '$MODE'"
exit 1 exit 1
;; ;;
esac esac

View File

@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -e
cat >access.yml <<EOF
pallav:
fixedArgsCommands:
build:
- base
- workspace
- all
clean:
status:
multiArgsCommands:
remove:
- palak
- param
- darshan
EOF
source ./validate_command_access.sh
testcase() {
local desc="$1"
shift
if validate_command pallav "$@"; then
echo "PASS: $desc"
else
echo "FAIL: $desc"
fi
}
testcase "build base (valid)" build base
testcase "build all (valid)" build all
testcase "build base workspace (invalid)" build base workspace || true
testcase "build (no arg, invalid)" build || true
testcase "clean (zero-arg, valid)" clean
testcase "clean with arg (invalid)" clean foo || true
testcase "remove palak (valid)" remove palak
testcase "remove param palak (valid, any order)" remove param palak
testcase "remove palak param darshan (valid, any order)" remove palak param darshan
testcase "remove (no arg, invalid)" remove || true
testcase "remove foo (invalid)" remove foo || true
testcase "remove palak palak (duplicate, invalid)" remove palak palak || true
testcase "status (zero-arg, valid)" status
testcase "status foo (invalid)" status foo || true

View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
validate_command() {
local PERSON="$1"
local cmd="$2"
shift 2
local tokens=("$@")
local yaml="access.yml"
# Check if fixedArgsCommands.<cmd> exists
local is_fixed
is_fixed="$(yq e ".\"$PERSON\".fixedArgsCommands | has(\"$cmd\")" "$yaml")"
# Check if multiArgsCommands.<cmd> exists
local is_multi
is_multi="$(yq e ".\"$PERSON\".multiArgsCommands | has(\"$cmd\")" "$yaml")"
if [[ "$is_fixed" != "true" && "$is_multi" != "true" ]]; then
echo "ERROR: Command '$cmd' not allowed for $PERSON" >&2
return 1
fi
# Exclude flags from positional args
local args=()
for tok in "${tokens[@]}"; do
[[ "$tok" == -* ]] && continue
args+=("$tok")
done
if [[ "$is_fixed" == "true" ]]; then
mapfile -t allowed < <(yq e ".\"$PERSON\".fixedArgsCommands.\"$cmd\"[]" "$yaml" 2>/dev/null)
local n_allowed="${#allowed[@]}"
if [[ $n_allowed -eq 0 ]]; then
# zero-arg command
if [[ ${#args[@]} -ne 0 ]]; then
echo "ERROR: Command '$cmd' takes no arguments" >&2
return 1
fi
else
# depth is 1: only one of the allowed choices must be present
if [[ ${#args[@]} -ne 1 ]]; then
echo "ERROR: Command '$cmd' requires exactly 1 argument: (${allowed[*]})" >&2
return 1
fi
local found=0
for want in "${allowed[@]}"; do
[[ "${args[0]}" == "$want" ]] && found=1 && break
done
if [[ $found -eq 0 ]]; then
echo "ERROR: Invalid argument '${args[0]}' for '$cmd'; allowed: (${allowed[*]})" >&2
return 1
fi
fi
return 0
fi
if [[ "$is_multi" == "true" ]]; then
mapfile -t allowed < <(yq e ".\"$PERSON\".multiArgsCommands.\"$cmd\"[]" "$yaml" 2>/dev/null)
local n_allowed="${#allowed[@]}"
if [[ ${#args[@]} -lt 1 || ${#args[@]} -gt $n_allowed ]]; then
echo "ERROR: Command '$cmd' requires 1 to $n_allowed arguments: (${allowed[*]})" >&2
return 1
fi
# Order doesn't matter, but all must be unique and from allowed.
# Build a set of allowed args.
declare -A allowed_set=()
for want in "${allowed[@]}"; do allowed_set["$want"]=1; done
declare -A seen=()
for a in "${args[@]}"; do
[[ -z "${allowed_set[$a]}" ]] && {
echo "ERROR: Invalid argument '$a' for '$cmd'; allowed: (${allowed[*]})" >&2
return 1
}
[[ -n "${seen[$a]}" ]] && {
echo "ERROR: Duplicate argument '$a' for '$cmd'" >&2
return 1
}
seen["$a"]=1
done
return 0
fi
}