it-tools-mcp 3.0.23 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.dockerhub.md +30 -17
- package/README.md +81 -33
- package/build/index.js +47 -15
- package/build/tools/ansible/ansible-inventory-generator/index.js +212 -0
- package/build/tools/ansible/ansible-playbook-validator/index.js +128 -0
- package/build/tools/ansible/ansible-reference/index.js +393 -0
- package/build/tools/ansible/ansible-vault-decrypt/index.js +137 -0
- package/build/tools/ansible/ansible-vault-encrypt/index.js +79 -0
- package/build/tools/color/color-hex-to-rgb/index.js +29 -0
- package/build/tools/{color.js → color/color-rgb-to-hex/index.js} +1 -27
- package/build/tools/crypto/basic-auth-generator/index.js +45 -0
- package/build/tools/crypto/bcrypt-hash/index.js +67 -0
- package/build/tools/crypto/bip39-generate/index.js +53 -0
- package/build/tools/crypto/hash-md5/index.js +19 -0
- package/build/tools/crypto/hash-sha1/index.js +19 -0
- package/build/tools/crypto/hash-sha256/index.js +19 -0
- package/build/tools/crypto/hash-sha512/index.js +19 -0
- package/build/tools/crypto/hmac-generator/index.js +37 -0
- package/build/tools/crypto/jwt-decode/index.js +41 -0
- package/build/tools/crypto/otp-code-generator/index.js +67 -0
- package/build/tools/crypto/password-generate/index.js +54 -0
- package/build/tools/crypto/token-generator/index.js +75 -0
- package/build/tools/dataFormat/html-to-markdown/index.js +34 -0
- package/build/tools/dataFormat/json-diff/index.js +94 -0
- package/build/tools/dataFormat/json-format/index.js +100 -0
- package/build/tools/dataFormat/json-minify/index.js +29 -0
- package/build/tools/dataFormat/json-to-csv/index.js +34 -0
- package/build/tools/dataFormat/json-to-toml/index.js +30 -0
- package/build/tools/dataFormat/markdown-to-html/index.js +32 -0
- package/build/tools/dataFormat/phone-format/index.js +35 -0
- package/build/tools/dataFormat/sql-format/index.js +37 -0
- package/build/tools/dataFormat/toml-to-json/index.js +29 -0
- package/build/tools/dataFormat/xml-format/index.js +44 -0
- package/build/tools/dataFormat/yaml-format/index.js +58 -0
- package/build/tools/{development.js → development/crontab-generate/index.js} +1 -129
- package/build/tools/development/html-prettifier/index.js +47 -0
- package/build/tools/development/javascript-prettifier/index.js +74 -0
- package/build/tools/development/list-converter/index.js +62 -0
- package/build/tools/development/markdown-toc-generator/index.js +53 -0
- package/build/tools/development/regex-tester/index.js +69 -0
- package/build/tools/docker/docker-compose-to-docker-run/index.js +138 -0
- package/build/tools/docker/docker-compose-validator/index.js +125 -0
- package/build/tools/docker/docker-reference/index.js +188 -0
- package/build/tools/docker/docker-run-to-docker-compose/index.js +117 -0
- package/build/tools/docker/traefik-compose-generator/index.js +98 -0
- package/build/tools/encoding/base64-decode/index.js +28 -0
- package/build/tools/encoding/base64-encode/index.js +16 -0
- package/build/tools/encoding/html-decode/index.js +21 -0
- package/build/tools/encoding/html-encode/index.js +21 -0
- package/build/tools/encoding/html-entities-extended/index.js +72 -0
- package/build/tools/encoding/text-to-binary/index.js +51 -0
- package/build/tools/encoding/url-decode/index.js +28 -0
- package/build/tools/encoding/url-encode/index.js +16 -0
- package/build/tools/forensic/file-type-identifier/index.js +90 -0
- package/build/tools/forensic/safelink-decoder/index.js +54 -0
- package/build/tools/forensic/url-fanger/index.js +52 -0
- package/build/tools/idGenerators/qr-generate/index.js +76 -0
- package/build/tools/idGenerators/svg-placeholder-generator/index.js +59 -0
- package/build/tools/idGenerators/ulid-generate/index.js +34 -0
- package/build/tools/idGenerators/uuid-generate/index.js +14 -0
- package/build/tools/math/math-evaluate/index.js +33 -0
- package/build/tools/math/number-base-converter/index.js +46 -0
- package/build/tools/math/percentage-calculator/index.js +50 -0
- package/build/tools/math/roman-numeral-converter/index.js +76 -0
- package/build/tools/math/temperature-converter/index.js +59 -0
- package/build/tools/math/unix-timestamp-converter/index.js +55 -0
- package/build/tools/network/cat/index.js +15 -0
- package/build/tools/network/cidr-to-ip-range/index.js +108 -0
- package/build/tools/network/curl/index.js +35 -0
- package/build/tools/network/dig/index.js +19 -0
- package/build/tools/network/grep/index.js +18 -0
- package/build/tools/network/head/index.js +17 -0
- package/build/tools/network/iban-validate/index.js +83 -0
- package/build/tools/network/ip-range-to-cidr/index.js +88 -0
- package/build/tools/network/ip-subnet-calculator/index.js +102 -0
- package/build/tools/network/ipv4-subnet-calc/index.js +112 -0
- package/build/tools/network/ipv6-subnet-calculator/index.js +104 -0
- package/build/tools/network/ipv6-ula-generator/index.js +65 -0
- package/build/tools/network/mac-address-generate/index.js +68 -0
- package/build/tools/network/nslookup/index.js +18 -0
- package/build/tools/network/ping/index.js +20 -0
- package/build/tools/network/ps/index.js +22 -0
- package/build/tools/network/random-port/index.js +53 -0
- package/build/tools/network/scp/index.js +134 -0
- package/build/tools/network/ssh/index.js +83 -0
- package/build/tools/network/tail/index.js +16 -0
- package/build/tools/network/telnet/index.js +45 -0
- package/build/tools/network/top/index.js +14 -0
- package/build/tools/network/url-parse/index.js +52 -0
- package/build/tools/physics/angle-converter/index.js +73 -0
- package/build/tools/physics/energy-converter/index.js +72 -0
- package/build/tools/physics/power-converter/index.js +71 -0
- package/build/tools/text/ascii-art-text/index.js +112 -0
- package/build/tools/text/distinct-words/index.js +30 -0
- package/build/tools/text/emoji-search/index.js +76 -0
- package/build/tools/text/lorem-ipsum-generator/index.js +87 -0
- package/build/tools/text/numeronym-generator/index.js +37 -0
- package/build/tools/text/slugify-string/index.js +44 -0
- package/build/tools/text/string-obfuscator/index.js +49 -0
- package/build/tools/text/text-camelcase/index.js +20 -0
- package/build/tools/text/text-capitalize/index.js +16 -0
- package/build/tools/text/text-diff/index.js +72 -0
- package/build/tools/text/text-kebabcase/index.js +20 -0
- package/build/tools/text/text-lowercase/index.js +15 -0
- package/build/tools/text/text-pascalcase/index.js +18 -0
- package/build/tools/text/text-snakecase/index.js +20 -0
- package/build/tools/text/text-stats/index.js +29 -0
- package/build/tools/text/text-to-nato-alphabet/index.js +57 -0
- package/build/tools/text/text-to-unicode/index.js +50 -0
- package/build/tools/text/text-to-unicode-names/index.js +34 -0
- package/build/tools/text/text-uppercase/index.js +15 -0
- package/build/tools/utility/css-prettifier/index.js +70 -0
- package/build/tools/utility/device-info/index.js +44 -0
- package/build/tools/utility/email-normalizer/index.js +73 -0
- package/build/tools/utility/http-status-codes/index.js +173 -0
- package/build/tools/utility/mime-types/index.js +121 -0
- package/build/tools/utility/port-numbers/index.js +106 -0
- package/build/tools/utility/rem-px-converter/index.js +63 -0
- package/package.json +3 -3
- package/build/tools/crypto.js +0 -445
- package/build/tools/dataFormat.js +0 -535
- package/build/tools/encoding.js +0 -240
- package/build/tools/idGenerators.js +0 -180
- package/build/tools/math.js +0 -310
- package/build/tools/network.js +0 -931
- package/build/tools/text.js +0 -678
- package/build/tools/utility.js +0 -407
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAnsiblePlaybookValidator(server) {
|
|
3
|
+
server.tool("ansible-playbook-validator", "Validate Ansible playbook syntax and structure", {
|
|
4
|
+
playbook: z.string().describe("Ansible playbook YAML content"),
|
|
5
|
+
}, async ({ playbook }) => {
|
|
6
|
+
if (!playbook?.trim()) {
|
|
7
|
+
return {
|
|
8
|
+
content: [
|
|
9
|
+
{
|
|
10
|
+
type: "text",
|
|
11
|
+
text: "Please provide playbook content to validate",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const yaml = await import('js-yaml');
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = yaml.load(playbook);
|
|
21
|
+
}
|
|
22
|
+
catch (yamlError) {
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: "text",
|
|
27
|
+
text: `YAML syntax error: ${yamlError.message}`,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const errors = [];
|
|
33
|
+
const warnings = [];
|
|
34
|
+
// Basic playbook structure validation
|
|
35
|
+
if (!Array.isArray(parsed)) {
|
|
36
|
+
errors.push('Playbook must be a YAML array of plays');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Validate each play
|
|
40
|
+
parsed.forEach((play, index) => {
|
|
41
|
+
const playNum = index + 1;
|
|
42
|
+
if (typeof play !== 'object' || play === null) {
|
|
43
|
+
errors.push(`Play ${playNum}: must be an object`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Check required fields
|
|
47
|
+
if (!play.name && !play.hosts) {
|
|
48
|
+
warnings.push(`Play ${playNum}: should have either 'name' or 'hosts' field`);
|
|
49
|
+
}
|
|
50
|
+
if (!play.hosts) {
|
|
51
|
+
errors.push(`Play ${playNum}: missing required 'hosts' field`);
|
|
52
|
+
}
|
|
53
|
+
// Validate tasks
|
|
54
|
+
if (play.tasks) {
|
|
55
|
+
if (!Array.isArray(play.tasks)) {
|
|
56
|
+
errors.push(`Play ${playNum}: 'tasks' must be an array`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
play.tasks.forEach((task, taskIndex) => {
|
|
60
|
+
const taskNum = taskIndex + 1;
|
|
61
|
+
if (typeof task !== 'object' || task === null) {
|
|
62
|
+
errors.push(`Play ${playNum}, Task ${taskNum}: must be an object`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Check for task action
|
|
66
|
+
const taskActions = Object.keys(task).filter(key => !['name', 'when', 'tags', 'become', 'become_user', 'vars', 'register', 'failed_when', 'changed_when', 'ignore_errors', 'notify'].includes(key));
|
|
67
|
+
if (taskActions.length === 0) {
|
|
68
|
+
errors.push(`Play ${playNum}, Task ${taskNum}: no action module specified`);
|
|
69
|
+
}
|
|
70
|
+
else if (taskActions.length > 1) {
|
|
71
|
+
warnings.push(`Play ${playNum}, Task ${taskNum}: multiple action modules found: ${taskActions.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
if (!task.name) {
|
|
74
|
+
warnings.push(`Play ${playNum}, Task ${taskNum}: should have a 'name' field for better readability`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Validate handlers
|
|
80
|
+
if (play.handlers) {
|
|
81
|
+
if (!Array.isArray(play.handlers)) {
|
|
82
|
+
errors.push(`Play ${playNum}: 'handlers' must be an array`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Check for common variables sections
|
|
86
|
+
if (play.vars && typeof play.vars !== 'object') {
|
|
87
|
+
errors.push(`Play ${playNum}: 'vars' must be an object`);
|
|
88
|
+
}
|
|
89
|
+
if (play.vars_files && !Array.isArray(play.vars_files)) {
|
|
90
|
+
errors.push(`Play ${playNum}: 'vars_files' must be an array`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
let result = '';
|
|
95
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
96
|
+
result = '✅ Playbook validation passed! No errors or warnings found.';
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
if (errors.length > 0) {
|
|
100
|
+
result += `❌ Errors found:\n${errors.map(error => ` • ${error}`).join('\n')}`;
|
|
101
|
+
}
|
|
102
|
+
if (warnings.length > 0) {
|
|
103
|
+
if (result)
|
|
104
|
+
result += '\n\n';
|
|
105
|
+
result += `⚠️ Warnings:\n${warnings.map(warning => ` • ${warning}`).join('\n')}`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: result,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: `Error validating playbook: ${error.message}`,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
export function registerAnsibleReference(server) {
|
|
2
|
+
server.tool("ansible-reference", "Get Ansible commands reference and cheatsheet", {}, async () => {
|
|
3
|
+
const reference = `# Ansible Quick Reference
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
\`\`\`bash
|
|
7
|
+
# Install via pip
|
|
8
|
+
pip install ansible
|
|
9
|
+
|
|
10
|
+
# Install via apt (Ubuntu/Debian)
|
|
11
|
+
sudo apt update
|
|
12
|
+
sudo apt install ansible
|
|
13
|
+
|
|
14
|
+
# Install via yum (CentOS/RHEL)
|
|
15
|
+
sudo yum install ansible
|
|
16
|
+
|
|
17
|
+
# Verify installation
|
|
18
|
+
ansible --version
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
## Configuration Files
|
|
22
|
+
- \`/etc/ansible/ansible.cfg\` - Global configuration
|
|
23
|
+
- \`~/.ansible.cfg\` - User configuration
|
|
24
|
+
- \`./ansible.cfg\` - Project configuration
|
|
25
|
+
- \`/etc/ansible/hosts\` - Default inventory file
|
|
26
|
+
|
|
27
|
+
## Basic Commands
|
|
28
|
+
\`\`\`bash
|
|
29
|
+
# Run ad-hoc command
|
|
30
|
+
ansible <hosts> -m <module> -a "<arguments>"
|
|
31
|
+
|
|
32
|
+
# Run playbook
|
|
33
|
+
ansible-playbook playbook.yml
|
|
34
|
+
|
|
35
|
+
# Check playbook syntax
|
|
36
|
+
ansible-playbook --syntax-check playbook.yml
|
|
37
|
+
|
|
38
|
+
# Dry run (check mode)
|
|
39
|
+
ansible-playbook --check playbook.yml
|
|
40
|
+
|
|
41
|
+
# Limit to specific hosts
|
|
42
|
+
ansible-playbook -l <host_pattern> playbook.yml
|
|
43
|
+
|
|
44
|
+
# Use specific inventory
|
|
45
|
+
ansible-playbook -i inventory.ini playbook.yml
|
|
46
|
+
|
|
47
|
+
# Ask for sudo password
|
|
48
|
+
ansible-playbook --ask-become-pass playbook.yml
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
## Inventory Examples
|
|
52
|
+
|
|
53
|
+
### INI Format
|
|
54
|
+
\`\`\`ini
|
|
55
|
+
[webservers]
|
|
56
|
+
web1.example.com
|
|
57
|
+
web2.example.com
|
|
58
|
+
|
|
59
|
+
[databases]
|
|
60
|
+
db1.example.com
|
|
61
|
+
db2.example.com
|
|
62
|
+
|
|
63
|
+
[production:children]
|
|
64
|
+
webservers
|
|
65
|
+
databases
|
|
66
|
+
|
|
67
|
+
[webservers:vars]
|
|
68
|
+
http_port=80
|
|
69
|
+
max_clients=200
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
### YAML Format
|
|
73
|
+
\`\`\`yaml
|
|
74
|
+
all:
|
|
75
|
+
children:
|
|
76
|
+
webservers:
|
|
77
|
+
hosts:
|
|
78
|
+
web1.example.com:
|
|
79
|
+
web2.example.com:
|
|
80
|
+
vars:
|
|
81
|
+
http_port: 80
|
|
82
|
+
max_clients: 200
|
|
83
|
+
databases:
|
|
84
|
+
hosts:
|
|
85
|
+
db1.example.com:
|
|
86
|
+
db2.example.com:
|
|
87
|
+
\`\`\`
|
|
88
|
+
|
|
89
|
+
## Playbook Structure
|
|
90
|
+
\`\`\`yaml
|
|
91
|
+
---
|
|
92
|
+
- name: Configure web servers
|
|
93
|
+
hosts: webservers
|
|
94
|
+
become: yes
|
|
95
|
+
vars:
|
|
96
|
+
http_port: 80
|
|
97
|
+
|
|
98
|
+
tasks:
|
|
99
|
+
- name: Install Apache
|
|
100
|
+
package:
|
|
101
|
+
name: apache2
|
|
102
|
+
state: present
|
|
103
|
+
|
|
104
|
+
- name: Start Apache service
|
|
105
|
+
service:
|
|
106
|
+
name: apache2
|
|
107
|
+
state: started
|
|
108
|
+
enabled: yes
|
|
109
|
+
|
|
110
|
+
handlers:
|
|
111
|
+
- name: restart apache
|
|
112
|
+
service:
|
|
113
|
+
name: apache2
|
|
114
|
+
state: restarted
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
## Common Modules
|
|
118
|
+
|
|
119
|
+
### Package Management
|
|
120
|
+
\`\`\`yaml
|
|
121
|
+
# Install package
|
|
122
|
+
- package:
|
|
123
|
+
name: nginx
|
|
124
|
+
state: present
|
|
125
|
+
|
|
126
|
+
# Install specific version
|
|
127
|
+
- package:
|
|
128
|
+
name: nginx=1.18.0
|
|
129
|
+
state: present
|
|
130
|
+
|
|
131
|
+
# Remove package
|
|
132
|
+
- package:
|
|
133
|
+
name: nginx
|
|
134
|
+
state: absent
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
137
|
+
### Service Management
|
|
138
|
+
\`\`\`yaml
|
|
139
|
+
# Start and enable service
|
|
140
|
+
- service:
|
|
141
|
+
name: nginx
|
|
142
|
+
state: started
|
|
143
|
+
enabled: yes
|
|
144
|
+
|
|
145
|
+
# Stop service
|
|
146
|
+
- service:
|
|
147
|
+
name: nginx
|
|
148
|
+
state: stopped
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
### File Operations
|
|
152
|
+
\`\`\`yaml
|
|
153
|
+
# Copy file
|
|
154
|
+
- copy:
|
|
155
|
+
src: /local/file.txt
|
|
156
|
+
dest: /remote/file.txt
|
|
157
|
+
owner: root
|
|
158
|
+
group: root
|
|
159
|
+
mode: '0644'
|
|
160
|
+
|
|
161
|
+
# Create directory
|
|
162
|
+
- file:
|
|
163
|
+
path: /path/to/directory
|
|
164
|
+
state: directory
|
|
165
|
+
mode: '0755'
|
|
166
|
+
|
|
167
|
+
# Create symlink
|
|
168
|
+
- file:
|
|
169
|
+
src: /path/to/source
|
|
170
|
+
dest: /path/to/link
|
|
171
|
+
state: link
|
|
172
|
+
\`\`\`
|
|
173
|
+
|
|
174
|
+
### Template
|
|
175
|
+
\`\`\`yaml
|
|
176
|
+
- template:
|
|
177
|
+
src: nginx.conf.j2
|
|
178
|
+
dest: /etc/nginx/nginx.conf
|
|
179
|
+
owner: root
|
|
180
|
+
group: root
|
|
181
|
+
mode: '0644'
|
|
182
|
+
notify: restart nginx
|
|
183
|
+
\`\`\`
|
|
184
|
+
|
|
185
|
+
### Command Execution
|
|
186
|
+
\`\`\`yaml
|
|
187
|
+
# Run command
|
|
188
|
+
- command: /bin/ls -la /tmp
|
|
189
|
+
|
|
190
|
+
# Run shell command
|
|
191
|
+
- shell: echo "Hello" > /tmp/hello.txt
|
|
192
|
+
|
|
193
|
+
# Raw command (no processing)
|
|
194
|
+
- raw: /bin/ls
|
|
195
|
+
\`\`\`
|
|
196
|
+
|
|
197
|
+
## Variables and Facts
|
|
198
|
+
|
|
199
|
+
### Variable Files
|
|
200
|
+
\`\`\`yaml
|
|
201
|
+
# vars.yml
|
|
202
|
+
database_name: myapp
|
|
203
|
+
database_user: myuser
|
|
204
|
+
database_password: mypass
|
|
205
|
+
\`\`\`
|
|
206
|
+
|
|
207
|
+
### Using Variables
|
|
208
|
+
\`\`\`yaml
|
|
209
|
+
- name: Create database
|
|
210
|
+
mysql_db:
|
|
211
|
+
name: "{{ database_name }}"
|
|
212
|
+
state: present
|
|
213
|
+
\`\`\`
|
|
214
|
+
|
|
215
|
+
### Gathering Facts
|
|
216
|
+
\`\`\`yaml
|
|
217
|
+
# Disable fact gathering
|
|
218
|
+
- hosts: all
|
|
219
|
+
gather_facts: no
|
|
220
|
+
|
|
221
|
+
# Use facts
|
|
222
|
+
- debug:
|
|
223
|
+
msg: "OS is {{ ansible_distribution }}"
|
|
224
|
+
\`\`\`
|
|
225
|
+
|
|
226
|
+
## Conditionals and Loops
|
|
227
|
+
|
|
228
|
+
### Conditionals
|
|
229
|
+
\`\`\`yaml
|
|
230
|
+
- name: Install package on Ubuntu
|
|
231
|
+
package:
|
|
232
|
+
name: apache2
|
|
233
|
+
state: present
|
|
234
|
+
when: ansible_distribution == "Ubuntu"
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
### Loops
|
|
238
|
+
\`\`\`yaml
|
|
239
|
+
# Simple loop
|
|
240
|
+
- name: Install packages
|
|
241
|
+
package:
|
|
242
|
+
name: "{{ item }}"
|
|
243
|
+
state: present
|
|
244
|
+
loop:
|
|
245
|
+
- nginx
|
|
246
|
+
- git
|
|
247
|
+
- curl
|
|
248
|
+
|
|
249
|
+
# Loop with dictionaries
|
|
250
|
+
- name: Create users
|
|
251
|
+
user:
|
|
252
|
+
name: "{{ item.name }}"
|
|
253
|
+
group: "{{ item.group }}"
|
|
254
|
+
loop:
|
|
255
|
+
- { name: 'alice', group: 'admins' }
|
|
256
|
+
- { name: 'bob', group: 'users' }
|
|
257
|
+
\`\`\`
|
|
258
|
+
|
|
259
|
+
## Handlers
|
|
260
|
+
\`\`\`yaml
|
|
261
|
+
tasks:
|
|
262
|
+
- name: Copy config file
|
|
263
|
+
copy:
|
|
264
|
+
src: nginx.conf
|
|
265
|
+
dest: /etc/nginx/nginx.conf
|
|
266
|
+
notify:
|
|
267
|
+
- restart nginx
|
|
268
|
+
- reload nginx
|
|
269
|
+
|
|
270
|
+
handlers:
|
|
271
|
+
- name: restart nginx
|
|
272
|
+
service:
|
|
273
|
+
name: nginx
|
|
274
|
+
state: restarted
|
|
275
|
+
|
|
276
|
+
- name: reload nginx
|
|
277
|
+
service:
|
|
278
|
+
name: nginx
|
|
279
|
+
state: reloaded
|
|
280
|
+
\`\`\`
|
|
281
|
+
|
|
282
|
+
## Vault (Encryption)
|
|
283
|
+
\`\`\`bash
|
|
284
|
+
# Create encrypted file
|
|
285
|
+
ansible-vault create secret.yml
|
|
286
|
+
|
|
287
|
+
# Edit encrypted file
|
|
288
|
+
ansible-vault edit secret.yml
|
|
289
|
+
|
|
290
|
+
# Encrypt existing file
|
|
291
|
+
ansible-vault encrypt file.yml
|
|
292
|
+
|
|
293
|
+
# Decrypt file
|
|
294
|
+
ansible-vault decrypt file.yml
|
|
295
|
+
|
|
296
|
+
# View encrypted file
|
|
297
|
+
ansible-vault view secret.yml
|
|
298
|
+
|
|
299
|
+
# Run playbook with vault
|
|
300
|
+
ansible-playbook --ask-vault-pass playbook.yml
|
|
301
|
+
ansible-playbook --vault-password-file vault_pass.txt playbook.yml
|
|
302
|
+
\`\`\`
|
|
303
|
+
|
|
304
|
+
## Roles Structure
|
|
305
|
+
\`\`\`
|
|
306
|
+
roles/
|
|
307
|
+
webserver/
|
|
308
|
+
tasks/
|
|
309
|
+
main.yml
|
|
310
|
+
handlers/
|
|
311
|
+
main.yml
|
|
312
|
+
templates/
|
|
313
|
+
nginx.conf.j2
|
|
314
|
+
files/
|
|
315
|
+
index.html
|
|
316
|
+
vars/
|
|
317
|
+
main.yml
|
|
318
|
+
defaults/
|
|
319
|
+
main.yml
|
|
320
|
+
meta/
|
|
321
|
+
main.yml
|
|
322
|
+
\`\`\`
|
|
323
|
+
|
|
324
|
+
## Best Practices
|
|
325
|
+
- Use descriptive task names
|
|
326
|
+
- Organize with roles and includes
|
|
327
|
+
- Use variables for configuration
|
|
328
|
+
- Always use version control
|
|
329
|
+
- Test with \`--check\` mode first
|
|
330
|
+
- Use handlers for service restarts
|
|
331
|
+
- Make playbooks idempotent
|
|
332
|
+
- Use tags for selective execution
|
|
333
|
+
- Keep secrets in vault files
|
|
334
|
+
|
|
335
|
+
## Common Patterns
|
|
336
|
+
|
|
337
|
+
### Rolling Updates
|
|
338
|
+
\`\`\`yaml
|
|
339
|
+
- hosts: webservers
|
|
340
|
+
serial: 1
|
|
341
|
+
max_fail_percentage: 25
|
|
342
|
+
\`\`\`
|
|
343
|
+
|
|
344
|
+
### Tags
|
|
345
|
+
\`\`\`yaml
|
|
346
|
+
- name: Install packages
|
|
347
|
+
package:
|
|
348
|
+
name: nginx
|
|
349
|
+
tags:
|
|
350
|
+
- install
|
|
351
|
+
- packages
|
|
352
|
+
\`\`\`
|
|
353
|
+
|
|
354
|
+
### Error Handling
|
|
355
|
+
\`\`\`yaml
|
|
356
|
+
- name: Risky task
|
|
357
|
+
command: /bin/risky-command
|
|
358
|
+
ignore_errors: yes
|
|
359
|
+
register: result
|
|
360
|
+
failed_when: result.rc != 0 and "expected error" not in result.stderr
|
|
361
|
+
\`\`\`
|
|
362
|
+
|
|
363
|
+
## Useful Commands
|
|
364
|
+
\`\`\`bash
|
|
365
|
+
# List hosts
|
|
366
|
+
ansible-inventory --list
|
|
367
|
+
|
|
368
|
+
# Check connectivity
|
|
369
|
+
ansible all -m ping
|
|
370
|
+
|
|
371
|
+
# Gather facts
|
|
372
|
+
ansible hostname -m setup
|
|
373
|
+
|
|
374
|
+
# Run with tags
|
|
375
|
+
ansible-playbook --tags "install,config" playbook.yml
|
|
376
|
+
|
|
377
|
+
# Skip tags
|
|
378
|
+
ansible-playbook --skip-tags "slow" playbook.yml
|
|
379
|
+
|
|
380
|
+
# Limit hosts
|
|
381
|
+
ansible-playbook -l "webservers:!web3" playbook.yml
|
|
382
|
+
\`\`\`
|
|
383
|
+
`;
|
|
384
|
+
return {
|
|
385
|
+
content: [
|
|
386
|
+
{
|
|
387
|
+
type: "text",
|
|
388
|
+
text: reference,
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { pbkdf2Sync } from "crypto";
|
|
3
|
+
export function registerAnsibleVaultDecrypt(server) {
|
|
4
|
+
server.tool("ansible-vault-decrypt", "Decrypt Ansible Vault encrypted text", {
|
|
5
|
+
encryptedText: z.string().describe("Ansible Vault encrypted text to decrypt"),
|
|
6
|
+
password: z.string().describe("Password for decryption"),
|
|
7
|
+
}, async ({ encryptedText, password }) => {
|
|
8
|
+
if (!encryptedText?.trim()) {
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: "text",
|
|
13
|
+
text: "Please provide encrypted text to decrypt",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (!password?.trim()) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: "Please provide a password for decryption",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
// Parse Ansible Vault format
|
|
30
|
+
const lines = encryptedText.trim().split('\n');
|
|
31
|
+
if (!lines[0].startsWith('$ANSIBLE_VAULT;')) {
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: "Invalid Ansible Vault format: missing header",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Extract vault header info
|
|
42
|
+
const headerParts = lines[0].split(';');
|
|
43
|
+
if (headerParts.length < 3) {
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: "Invalid Ansible Vault format: malformed header",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const version = headerParts[1];
|
|
54
|
+
const cipher = headerParts[2];
|
|
55
|
+
const vaultId = headerParts.length > 3 ? headerParts[3] : null;
|
|
56
|
+
if (version !== '1.1' && version !== '1.2') {
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `Unsupported Ansible Vault version: ${version}`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (cipher !== 'AES256') {
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `Unsupported cipher: ${cipher}`,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Combine data lines and decode base64
|
|
77
|
+
const dataLines = lines.slice(1).join('');
|
|
78
|
+
const combined = Buffer.from(dataLines, 'base64');
|
|
79
|
+
if (combined.length < 80) { // 32 (salt) + 16 (iv) + 32 (mac) = 80 minimum
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: "text",
|
|
84
|
+
text: "Invalid Ansible Vault format: insufficient data",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Extract components
|
|
90
|
+
const salt = combined.subarray(0, 32);
|
|
91
|
+
const iv = combined.subarray(32, 48);
|
|
92
|
+
const encryptedData = combined.subarray(48, -32);
|
|
93
|
+
const mac = combined.subarray(-32);
|
|
94
|
+
// Derive key using PBKDF2
|
|
95
|
+
const key = pbkdf2Sync(password, salt, 10000, 32, 'sha256');
|
|
96
|
+
// Verify HMAC
|
|
97
|
+
const crypto = await import('crypto');
|
|
98
|
+
const hmac = crypto.createHmac('sha256', key);
|
|
99
|
+
hmac.update(salt);
|
|
100
|
+
hmac.update(iv);
|
|
101
|
+
hmac.update(encryptedData);
|
|
102
|
+
const calculatedMac = hmac.digest();
|
|
103
|
+
if (!calculatedMac.equals(mac)) {
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: "Decryption failed: invalid password or corrupted data",
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Decrypt using AES-256-CTR
|
|
114
|
+
const decipher = crypto.createDecipheriv('aes-256-ctr', key, iv);
|
|
115
|
+
let decrypted = decipher.update(encryptedData, undefined, 'utf8');
|
|
116
|
+
decrypted += decipher.final('utf8');
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: "text",
|
|
121
|
+
text: decrypted,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: `Error decrypting text: ${error.message}`,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|