[fix] default parameter run records parameters in session for replay[fix] remove default parameters for several cubes
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# user-add
|
||||
|
||||
**Add a user with Fish shell and tools**
|
||||
|
||||
## Purpose
|
||||
|
||||
This cube creates a new user account with a modern shell environment (Fish), SSH key authentication, and enhanced productivity tools pre-configured.
|
||||
|
||||
## What This Cube Does
|
||||
|
||||
1. **Creates a new user account**
|
||||
- Sets up home directory with proper permissions
|
||||
- Configures password authentication
|
||||
- Adds user to specified groups (e.g., `docker`, `sudo`)
|
||||
- Sets Fish as the default login shell
|
||||
|
||||
2. **Configures SSH access**
|
||||
- Deploys the specified SSH public key for passwordless authentication
|
||||
- Creates `.ssh` directory with proper permissions
|
||||
- Sets up SSH config file
|
||||
- Configures SSH agent auto-loading for Fish shell
|
||||
|
||||
3. **Installs Fish shell enhancements**
|
||||
- Installs **Oh My Fish** (OMF) - Fish shell framework with themes and plugins
|
||||
- Deploys custom Fish configuration (`config.fish`)
|
||||
- Sets up Fish rc directory for modular configurations
|
||||
|
||||
4. **Creates workspace directories**
|
||||
- Creates `/home/{USER}/tmp` directory for temporary files
|
||||
|
||||
## Configuration
|
||||
|
||||
### Parameters
|
||||
|
||||
- **USER** (string, auto-generated)
|
||||
- Username for the new user account
|
||||
- Default: `userXXXXX` (randomly generated 5-character suffix)
|
||||
|
||||
- **PASSWORD** (string, **secret**)
|
||||
- Password for the new user account
|
||||
- Default: the literal `changeme` — a placeholder, not a credential. Change it
|
||||
on first login, or pass a real one.
|
||||
- Declared in the manifest's `secrets`, so it is never written to a session or
|
||||
history file and is masked in printed commands. A replay asks for it again.
|
||||
- It used to default to a randomly generated password. That was removed: since
|
||||
the value is not recorded, an unattended run created an account with a
|
||||
credential nobody had seen, and replaying that run produced a different one.
|
||||
|
||||
- **GROUPS** (string, default: `''`)
|
||||
- Comma-separated list of additional groups (e.g., `"docker,sudo"`)
|
||||
- Common groups:
|
||||
- `docker` - Run Docker without sudo
|
||||
- `sudo` - Administrative privileges
|
||||
- `www-data` - Web server file access
|
||||
|
||||
- **PUBKEY** (string, **required** — no default)
|
||||
- SSH public key to authorize for the user
|
||||
- Should be your public key for passwordless SSH access
|
||||
- There is deliberately no default. It used to be a specific personal key, so
|
||||
accepting the default authorized *someone else's* key on the new account.
|
||||
No key would be a sensible guess, so the cube asks instead.
|
||||
- Because it is required, `--use-defaults` refuses to run this cube unless
|
||||
`PUBKEY` comes from `env` in `.nopyrc.json`, a dependency, or a hook.
|
||||
- Submitting an empty value at the prompt authorizes no key at all (the account
|
||||
is still created, with password login only).
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **apt:essentials** - Provides Fish shell and basic tools
|
||||
|
||||
## What is Fish?
|
||||
|
||||
Fish (Friendly Interactive Shell) is a modern command-line shell that focuses on usability:
|
||||
|
||||
- **Smart autosuggestions**: Suggests commands as you type based on history
|
||||
- **Syntax highlighting**: Color-codes commands in real-time
|
||||
- **Tab completions**: Comprehensive, discoverable command completions
|
||||
- **No configuration needed**: Works great out of the box
|
||||
|
||||
## Post-Installation
|
||||
|
||||
After deployment:
|
||||
- SSH into the server as the new user: `ssh {USER}@server`
|
||||
- Your SSH key will be pre-authorized (no password needed if using key)
|
||||
- Fish shell will start automatically with OMF installed
|
||||
- SSH agent auto-loads to manage your SSH keys
|
||||
|
||||
## Notes
|
||||
|
||||
- The user's home directory is created at `/home/{USER}`
|
||||
- Fish configuration is stored in `/home/{USER}/.config/fish/`
|
||||
- Oh My Fish provides package management: `omf install <package>`
|
||||
- To switch shells: `chsh -s /bin/bash` (or back to fish: `chsh -s /usr/bin/fish`)
|
||||
|
||||
---
|
||||
|
||||
# 📌 Most Useful Fish Key Bindings (with Fisher Extensions)
|
||||
|
||||
## 🐟 Default Fish Key Bindings
|
||||
|
||||
- `Ctrl + C` → Cancel the current command
|
||||
- `Ctrl + D` → Exit the shell (or logout if in SSH)
|
||||
- `Ctrl + L` → Clear the terminal
|
||||
- `Ctrl + R` → Search command history (enhanced by `fzf.fish`)
|
||||
- `Ctrl + U` → Delete the entire command line
|
||||
- `Ctrl + W` → Delete the last word
|
||||
- `Alt + ← / →` → Move backward/forward by a word
|
||||
|
||||
## 🔍 Enhanced with `fzf.fish`
|
||||
|
||||
- `Ctrl + R` → **Fuzzy search command history**
|
||||
- `Ctrl + T` → **Fuzzy search and insert file path**
|
||||
- `Alt + C` → **Fuzzy search directories (`cd` with `z`)**
|
||||
|
||||
## 📂 Directory Navigation (with `z`)
|
||||
|
||||
- `z <dir>` → Jump to a frequently used directory
|
||||
- `z -l` → List most-used directories
|
||||
- `z -c` → Remove a directory from `z`'s database
|
||||
|
||||
## 🔄 Process & Job Management
|
||||
|
||||
- `Ctrl + Z` → Suspend the current process
|
||||
- `fg` → Bring a suspended process back to foreground
|
||||
- `jobs` → List background jobs
|
||||
|
||||
## 🎨 Other Handy Shortcuts
|
||||
|
||||
- `fish_vi_key_bindings` → Enable Vi mode (press `Esc` for normal mode)
|
||||
- `Ctrl + G` → Show Git status (if using `fzf.fish`)
|
||||
- `Ctrl + E` → Edit command line in `$EDITOR`
|
||||
|
||||
## ⚙️ Useful Commands for Key Binding
|
||||
|
||||
```fish
|
||||
# Set Fish default key bindings
|
||||
fish_default_key_bindings
|
||||
|
||||
# Enable Vi mode
|
||||
fish_vi_key_bindings
|
||||
|
||||
# Rebind a custom key (Example: Ctrl + G for git status)
|
||||
bind \cg 'git status'
|
||||
@@ -0,0 +1,10 @@
|
||||
if status is-interactive
|
||||
# Commands to run in interactive sessions can go here
|
||||
# Execute all scripts in ~/.config/fish/rc/ on shell startup
|
||||
for script in ~/.config/fish/rc/*.fish
|
||||
if test -f $script
|
||||
source $script
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
from pyinfra import host
|
||||
from pyinfra.operations import server, files, apt
|
||||
from io import StringIO
|
||||
|
||||
# Define the username, password, and public key for the new admin user
|
||||
USER = host.data.USER
|
||||
HOME_DIR = f"/home/{USER}"
|
||||
TMP_DIR = f"{HOME_DIR}/tmp"
|
||||
PASSWORD = host.data.PASSWORD
|
||||
# An empty submission at the prompt must not become an empty authorized_keys
|
||||
# line, so an absent key means no key rather than a blank one.
|
||||
PUBKEY = host.data.PUBKEY
|
||||
PUBKEYS = [PUBKEY] if PUBKEY and str(PUBKEY).strip() else []
|
||||
GROUPS = list(filter(None, map(str.strip, str(host.data.GROUPS).split())))
|
||||
FISH_PATH = "/usr/bin/fish"
|
||||
FISH_CONFIG_DIR = f"{HOME_DIR}/.config/fish"
|
||||
FISH_CONFIG_FILE = f"{FISH_CONFIG_DIR}/config.fish"
|
||||
FISH_RC_DIR = f"{FISH_CONFIG_DIR}/rc"
|
||||
SSH_AGENT_SCRIPT = f"{FISH_RC_DIR}/ssh-agent.fish"
|
||||
|
||||
apt.packages(
|
||||
name='Ensure fish shell is installed',
|
||||
packages=[ 'fish'],
|
||||
_sudo=True
|
||||
)
|
||||
|
||||
|
||||
# Ensure the user exists with a login shell
|
||||
server.user(
|
||||
name=f"Create user {USER} [{GROUPS}]",
|
||||
present=True,
|
||||
user=USER,
|
||||
password=PASSWORD,
|
||||
create_home=True,
|
||||
groups=GROUPS,
|
||||
shell=FISH_PATH,
|
||||
public_keys=PUBKEYS,
|
||||
_sudo=True
|
||||
)
|
||||
|
||||
for dir in [f"{HOME_DIR}/.ssh", FISH_RC_DIR, TMP_DIR]:
|
||||
files.directory(
|
||||
name=f"Ensure {dir} directory exists",
|
||||
path=dir,
|
||||
present=True,
|
||||
mode=700,
|
||||
user=USER,
|
||||
group=USER,
|
||||
_sudo=True,
|
||||
_sudo_user=USER,
|
||||
_use_sudo_login=True
|
||||
)
|
||||
|
||||
files.file(
|
||||
name="Ensure .ssh/config exists",
|
||||
path=f"{HOME_DIR}/.ssh/config",
|
||||
present=True,
|
||||
user=USER,
|
||||
group=USER,
|
||||
_sudo=True
|
||||
)
|
||||
|
||||
server.shell(
|
||||
name=f"Install OMF(Oh My Fish) for {USER}",
|
||||
commands=[
|
||||
f"curl https://raw.githubusercontent.com/oh-my-fish/oh-my-fish/master/bin/install > install-omf",
|
||||
f"fish install-omf --yes --noninteractive",
|
||||
],
|
||||
_sudo=True,
|
||||
_sudo_user=USER,
|
||||
_use_sudo_login=True
|
||||
)
|
||||
|
||||
files.put(
|
||||
name="Add SSH agent auto-load script to Fish rc directory",
|
||||
src="ssh-agent.fish",
|
||||
dest=SSH_AGENT_SCRIPT,
|
||||
user=USER,
|
||||
group=USER,
|
||||
mode="755", # Make it executable
|
||||
_sudo=True,
|
||||
|
||||
)
|
||||
|
||||
files.put(
|
||||
name="Add custom config.fish",
|
||||
src="config.fish",
|
||||
dest=FISH_CONFIG_FILE,
|
||||
user=USER,
|
||||
group=USER,
|
||||
mode="755", # Make it executable
|
||||
_sudo=True,
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Manifest, uniqid } from '@bitsquare/nopy-cube';
|
||||
import { z } from 'zod';
|
||||
|
||||
export default Manifest({
|
||||
id: 'user:add',
|
||||
name: 'Add a user with fish shell and tools',
|
||||
dependencies: () => ['apt:essentials'],
|
||||
secrets: ['PASSWORD'],
|
||||
schema: z.object({
|
||||
USER: z
|
||||
.string()
|
||||
.describe('Username for the new user account')
|
||||
.default(() => `user${uniqid(5)}`),
|
||||
// A fixed placeholder, not a generated one: the password is never recorded
|
||||
// in a session, so a generated default meant every run produced credentials
|
||||
// nobody had seen and a replay produced different ones again.
|
||||
PASSWORD: z.string().describe('Password for the new user account').default('changeme'),
|
||||
GROUPS: z
|
||||
.string()
|
||||
.describe('Comma-separated list of additional groups (e.g., "docker,sudo")')
|
||||
.default(''),
|
||||
// No default on purpose. This used to carry a specific personal key, which
|
||||
// meant an unattended run authorised someone else's key on the new account.
|
||||
// Leaving it required makes `--use-defaults` refuse by name instead of
|
||||
// guessing, and there is no key that would be a sensible guess.
|
||||
PUBKEY: z.string().describe('SSH public key to authorize for the user'),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
# Start SSH agent if not already running
|
||||
if not set -q SSH_AUTH_SOCK
|
||||
eval (ssh-agent -c)
|
||||
end
|
||||
|
||||
# Add all private SSH keys in ~/.ssh to the agent
|
||||
for key in ~/.ssh/id_*;
|
||||
if test -f $key; and not string match -q "*pub" $key
|
||||
ssh-add $key 2>/dev/null
|
||||
end
|
||||
end
|
||||
# Export user and group ID for Docker
|
||||
set -x UID (id -u)
|
||||
set -x GID (id -g)
|
||||
@@ -0,0 +1,52 @@
|
||||
# user:edit
|
||||
|
||||
**Modify an existing user's password or group membership**
|
||||
|
||||
## Purpose
|
||||
|
||||
This cube allows you to update existing user accounts on the target system. It can be used to change passwords, add users to new groups (like `docker` or `sudo`), or revoke group memberships.
|
||||
|
||||
## What This Cube Does
|
||||
|
||||
1. Identifies the existing user on the target system
|
||||
2. Updates the user's password if `PASSWORD` is provided
|
||||
3. Adds the user to the groups specified in `GROUPS`
|
||||
4. Removes the user from the groups specified in `GROUPS_ABSENT`
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Type | Description | Required |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `USER` | `string` | The username of the account to modify | Yes |
|
||||
| `PASSWORD` | `string` | New password for the user. **Secret**: never recorded in a session or history file, masked in printed commands, re-prompted on replay. | No |
|
||||
| `GROUPS` | `string` | Comma-separated list of groups to ADD (e.g., `docker,sudo`) | No |
|
||||
| `GROUPS_ABSENT` | `string` | Comma-separated list of groups to REMOVE | No |
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `apt/essentials`: Standard system utilities.
|
||||
|
||||
## Usage
|
||||
|
||||
### Changing a Password
|
||||
|
||||
```bash
|
||||
nopy install user:edit --env USER=myuser --env PASSWORD=newsecurepassword
|
||||
```
|
||||
|
||||
### Adding a User to the Docker Group
|
||||
|
||||
```bash
|
||||
nopy install user:edit --env USER=myuser --env GROUPS=docker
|
||||
```
|
||||
|
||||
### Revoking Sudo Access
|
||||
|
||||
```bash
|
||||
nopy install user:edit --env USER=myuser --env GROUPS_ABSENT=sudo
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- When setting passwords via the CLI, they may be visible in your shell history. Consider using a session file or interactive prompts for sensitive values.
|
||||
- Changing your own user's groups or password may require a re-login to take full effect.
|
||||
@@ -0,0 +1,24 @@
|
||||
from pyinfra import host
|
||||
from pyinfra.operations import server
|
||||
|
||||
# [agnt://cogen/cogen/user-edit-2]{cartridge: "ansiblings/cubes", action: "generated", status: "generated"}
|
||||
|
||||
"""
|
||||
Deployment script for user:edit.
|
||||
Updates password and group membership for an existing user.
|
||||
"""
|
||||
|
||||
USER = host.data.USER
|
||||
PASSWORD = host.data.get('PASSWORD')
|
||||
GROUPS = [g.strip() for g in str(host.data.get('GROUPS', '')).split(',') if g.strip()]
|
||||
GROUPS_ABSENT = [g.strip() for g in str(host.data.get('GROUPS_ABSENT', '')).split(',') if g.strip()]
|
||||
|
||||
# Update user details
|
||||
server.user(
|
||||
name=f"Update user {USER}",
|
||||
user=USER,
|
||||
password=PASSWORD,
|
||||
groups=GROUPS,
|
||||
groups_absent=GROUPS_ABSENT,
|
||||
_sudo=True,
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Manifest } from '@bitsquare/nopy-cube';
|
||||
import { z } from 'zod';
|
||||
|
||||
// [agnt://cogen/cogen/user-edit-1]{cartridge: "ansiblings/cubes", action: "generated", status: "generated"}
|
||||
|
||||
/**
|
||||
* Manifest for the user:edit cube.
|
||||
* Allows modifying existing user accounts (password, groups).
|
||||
*/
|
||||
export default Manifest({
|
||||
id: 'user:edit',
|
||||
name: 'user:edit - Modify an existing user account',
|
||||
dependencies: () => [],
|
||||
secrets: ['PASSWORD'],
|
||||
schema: z.object({
|
||||
USER: z.string().describe('The username of the account to modify'),
|
||||
PASSWORD: z.string().optional().describe('New password for the user (optional)'),
|
||||
GROUPS: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Comma-separated list of groups the user SHOULD be in (optional)'),
|
||||
GROUPS_ABSENT: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Comma-separated list of groups to REMOVE from the user (optional)'),
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user