server-hardening-checklist 1.0.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/index.js +223 -0
  4. package/package.json +21 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dargslan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # πŸ›‘οΈ Server Hardening Checklist
2
+
3
+ [![npm](https://img.shields.io/npm/v/server-hardening-checklist)](https://www.npmjs.com/package/server-hardening-checklist)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ **Interactive Linux server security hardening checklist in your terminal.** Walk through 50+ security checks across 9 categories and get a security grade for your server.
7
+
8
+ ## Quick Start
9
+
10
+ ```bash
11
+ npx server-hardening-checklist
12
+ ```
13
+
14
+ ## Features
15
+
16
+ - πŸ›‘οΈ **50+ security checks** across 9 categories
17
+ - πŸ”‘ **SSH Hardening** β€” root login, key auth, port, timeouts
18
+ - πŸ›‘οΈ **Firewall** β€” iptables/ufw, default deny, SYN flood protection
19
+ - πŸ‘€ **User Management** β€” passwords, sudo, umask, PAM
20
+ - πŸ“¦ **System Updates** β€” auto-updates, kernel, package cleanup
21
+ - πŸ“‚ **File System** β€” permissions, SUID/SGID, /tmp hardening
22
+ - 🌐 **Network** β€” IP forwarding, ICMP, source routing
23
+ - πŸ“Š **Logging & Auditing** β€” auditd, fail2ban, log rotation
24
+ - βš™οΈ **Services** β€” disable unnecessary, check listeners
25
+ - πŸ•ΈοΈ **Web Server** β€” headers, TLS, directory listing, rate limiting
26
+ - πŸ† **Security grade** (A-F) based on your responses
27
+ - ⚠️ **Critical item tracking** β€” never miss important fixes
28
+ - πŸ”§ **Check commands** β€” exact commands to verify each item
29
+ - πŸš€ Zero dependencies
30
+
31
+ ## Audit Modes
32
+
33
+ | Mode | Description |
34
+ |------|-------------|
35
+ | Full Audit | All 50+ items across all sections |
36
+ | Critical Only | Only critical/must-do items |
37
+ | By Section | Focus on one category at a time |
38
+ | View Commands | See all check commands (no interaction) |
39
+
40
+ ## Priority Levels
41
+
42
+ - πŸ”΄ **CRITICAL** β€” Must fix immediately, major security risk
43
+ - 🟑 **RECOMMENDED** β€” Should implement, significant improvement
44
+ - βšͺ **OPTIONAL** β€” Nice to have, depends on use case
45
+
46
+ ## Who Is This For?
47
+
48
+ - πŸ–₯️ **System administrators** hardening production servers
49
+ - πŸ” **Security auditors** performing baseline checks
50
+ - πŸŽ“ **Students** learning server security best practices
51
+ - πŸ’Ό **DevOps engineers** setting up new servers
52
+
53
+ ## More Tools
54
+
55
+ - 🐧 `npx dargslan-linux-quiz` β€” Linux Quiz
56
+ - βš™οΈ `npx dargslan-sysadmin-quiz` β€” SysAdmin Quiz
57
+ - 🐳 `npx dargslan-devops-quiz` β€” DevOps Quiz
58
+ - πŸ”’ `npx dargslan-security-quiz` β€” Security Quiz
59
+ - πŸ“– `npx linux-cheatsheet-cli` β€” Command Reference
60
+
61
+ ## Learn More
62
+
63
+ - πŸ“š [210+ IT eBooks](https://dargslan.com/books)
64
+ - πŸ“„ [260+ Cheat Sheets](https://dargslan.com/cheat-sheets)
65
+ - πŸ“ [IT Blog](https://dargslan.com/blog)
66
+
67
+ ## License
68
+
69
+ MIT β€” [dargslan.com](https://dargslan.com)
package/index.js ADDED
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+
3
+ const readline = require('readline');
4
+
5
+ const c = {
6
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
7
+ red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
8
+ blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m',
9
+ bgGreen: '\x1b[42m', bgRed: '\x1b[41m', bgYellow: '\x1b[43m',
10
+ };
11
+
12
+ const SECTIONS = [
13
+ {
14
+ name: 'SSH Hardening', icon: 'πŸ”‘',
15
+ items: [
16
+ { id: 'ssh-1', text: 'Disable root login (PermitRootLogin no)', priority: 'critical', config: '/etc/ssh/sshd_config', command: "grep '^PermitRootLogin' /etc/ssh/sshd_config" },
17
+ { id: 'ssh-2', text: 'Use key-based authentication only (PasswordAuthentication no)', priority: 'critical', config: '/etc/ssh/sshd_config', command: "grep '^PasswordAuthentication' /etc/ssh/sshd_config" },
18
+ { id: 'ssh-3', text: 'Change default SSH port', priority: 'recommended', config: '/etc/ssh/sshd_config', command: "grep '^Port' /etc/ssh/sshd_config" },
19
+ { id: 'ssh-4', text: 'Set MaxAuthTries to 3', priority: 'recommended', config: '/etc/ssh/sshd_config', command: "grep '^MaxAuthTries' /etc/ssh/sshd_config" },
20
+ { id: 'ssh-5', text: 'Disable X11Forwarding', priority: 'recommended', config: '/etc/ssh/sshd_config', command: "grep '^X11Forwarding' /etc/ssh/sshd_config" },
21
+ { id: 'ssh-6', text: 'Set LoginGraceTime to 60', priority: 'optional', config: '/etc/ssh/sshd_config', command: "grep '^LoginGraceTime' /etc/ssh/sshd_config" },
22
+ { id: 'ssh-7', text: 'Use SSH Protocol 2 only', priority: 'critical', config: '/etc/ssh/sshd_config', command: "grep '^Protocol' /etc/ssh/sshd_config" },
23
+ { id: 'ssh-8', text: 'Set idle timeout (ClientAliveInterval 300)', priority: 'recommended', config: '/etc/ssh/sshd_config', command: "grep '^ClientAliveInterval' /etc/ssh/sshd_config" },
24
+ ],
25
+ },
26
+ {
27
+ name: 'Firewall', icon: 'πŸ›‘οΈ',
28
+ items: [
29
+ { id: 'fw-1', text: 'Enable firewall (ufw/firewalld/iptables)', priority: 'critical', command: 'ufw status || firewall-cmd --state || iptables -L -n' },
30
+ { id: 'fw-2', text: 'Default deny incoming traffic', priority: 'critical', command: 'iptables -L INPUT | head -1' },
31
+ { id: 'fw-3', text: 'Allow only necessary ports (22, 80, 443)', priority: 'critical', command: 'ss -tlnp' },
32
+ { id: 'fw-4', text: 'Block ICMP ping (optional, depends on use case)', priority: 'optional', command: "sysctl net.ipv4.icmp_echo_ignore_all" },
33
+ { id: 'fw-5', text: 'Enable SYN flood protection', priority: 'recommended', command: "sysctl net.ipv4.tcp_syncookies" },
34
+ { id: 'fw-6', text: 'Log dropped packets', priority: 'recommended', command: 'iptables -L -n | grep LOG' },
35
+ ],
36
+ },
37
+ {
38
+ name: 'User Management', icon: 'πŸ‘€',
39
+ items: [
40
+ { id: 'user-1', text: 'Remove or lock unused user accounts', priority: 'critical', command: "awk -F: '$3 >= 1000 {print $1}' /etc/passwd" },
41
+ { id: 'user-2', text: 'Enforce strong password policy', priority: 'critical', config: '/etc/security/pwquality.conf', command: 'grep minlen /etc/security/pwquality.conf' },
42
+ { id: 'user-3', text: 'Set password expiration (90 days)', priority: 'recommended', config: '/etc/login.defs', command: 'grep PASS_MAX_DAYS /etc/login.defs' },
43
+ { id: 'user-4', text: 'Restrict sudo access to necessary users only', priority: 'critical', command: 'grep -v "^#" /etc/sudoers | grep -v "^$"' },
44
+ { id: 'user-5', text: 'Set umask to 027 or 077', priority: 'recommended', command: 'umask' },
45
+ { id: 'user-6', text: 'Disable guest account', priority: 'recommended', command: 'id guest 2>&1' },
46
+ ],
47
+ },
48
+ {
49
+ name: 'System Updates', icon: 'πŸ“¦',
50
+ items: [
51
+ { id: 'upd-1', text: 'Enable automatic security updates', priority: 'critical', command: 'apt list --installed 2>/dev/null | grep unattended-upgrades || dnf list installed | grep dnf-automatic' },
52
+ { id: 'upd-2', text: 'Check for and apply pending updates', priority: 'critical', command: 'apt list --upgradable 2>/dev/null || dnf check-update' },
53
+ { id: 'upd-3', text: 'Remove unnecessary packages', priority: 'recommended', command: 'apt autoremove --dry-run 2>/dev/null || dnf autoremove --assumeno' },
54
+ { id: 'upd-4', text: 'Check kernel version is current', priority: 'recommended', command: 'uname -r' },
55
+ { id: 'upd-5', text: 'Enable and check reboot-required notifications', priority: 'optional', command: 'ls /var/run/reboot-required 2>&1' },
56
+ ],
57
+ },
58
+ {
59
+ name: 'File System Security', icon: 'πŸ“‚',
60
+ items: [
61
+ { id: 'fs-1', text: 'Set noexec,nosuid,nodev on /tmp', priority: 'critical', command: 'mount | grep /tmp' },
62
+ { id: 'fs-2', text: 'Set proper permissions on /etc/shadow (640)', priority: 'critical', command: 'ls -la /etc/shadow' },
63
+ { id: 'fs-3', text: 'Set proper permissions on /etc/passwd (644)', priority: 'critical', command: 'ls -la /etc/passwd' },
64
+ { id: 'fs-4', text: 'Find and review SUID/SGID files', priority: 'recommended', command: 'find / -perm /4000 -type f 2>/dev/null | head -10' },
65
+ { id: 'fs-5', text: 'Disable USB storage (if not needed)', priority: 'optional', command: 'lsmod | grep usb_storage' },
66
+ { id: 'fs-6', text: 'Set sticky bit on world-writable directories', priority: 'recommended', command: 'find / -type d -perm -0002 -not -perm -1000 2>/dev/null | head -5' },
67
+ ],
68
+ },
69
+ {
70
+ name: 'Network Hardening', icon: '🌐',
71
+ items: [
72
+ { id: 'net-1', text: 'Disable IP forwarding (unless router)', priority: 'critical', command: 'sysctl net.ipv4.ip_forward' },
73
+ { id: 'net-2', text: 'Disable ICMP redirects', priority: 'recommended', command: 'sysctl net.ipv4.conf.all.accept_redirects' },
74
+ { id: 'net-3', text: 'Enable reverse path filtering', priority: 'recommended', command: 'sysctl net.ipv4.conf.all.rp_filter' },
75
+ { id: 'net-4', text: 'Disable source routing', priority: 'critical', command: 'sysctl net.ipv4.conf.all.accept_source_route' },
76
+ { id: 'net-5', text: 'Use DNS-over-TLS or DNS-over-HTTPS', priority: 'optional', command: "grep 'DNS=' /etc/systemd/resolved.conf 2>/dev/null" },
77
+ ],
78
+ },
79
+ {
80
+ name: 'Logging & Auditing', icon: 'πŸ“Š',
81
+ items: [
82
+ { id: 'log-1', text: 'Enable and configure auditd', priority: 'critical', command: 'systemctl is-active auditd 2>/dev/null || echo "auditd not installed"' },
83
+ { id: 'log-2', text: 'Configure log rotation (logrotate)', priority: 'recommended', command: 'ls /etc/logrotate.d/' },
84
+ { id: 'log-3', text: 'Send logs to remote syslog server', priority: 'recommended', command: "grep '@' /etc/rsyslog.conf 2>/dev/null | head -3" },
85
+ { id: 'log-4', text: 'Monitor auth.log / secure for brute force', priority: 'critical', command: 'tail -5 /var/log/auth.log 2>/dev/null || tail -5 /var/log/secure 2>/dev/null' },
86
+ { id: 'log-5', text: 'Install and configure fail2ban', priority: 'critical', command: 'systemctl is-active fail2ban 2>/dev/null || echo "fail2ban not installed"' },
87
+ { id: 'log-6', text: 'Set up login alerts (email on SSH login)', priority: 'optional', command: "grep 'login' /etc/pam.d/sshd 2>/dev/null | head -3" },
88
+ ],
89
+ },
90
+ {
91
+ name: 'Services & Processes', icon: 'βš™οΈ',
92
+ items: [
93
+ { id: 'svc-1', text: 'Disable unnecessary services', priority: 'critical', command: 'systemctl list-unit-files --state=enabled --type=service' },
94
+ { id: 'svc-2', text: 'Check for listening services on all interfaces', priority: 'critical', command: 'ss -tlnp | grep 0.0.0.0' },
95
+ { id: 'svc-3', text: 'Ensure NTP synchronization is active', priority: 'recommended', command: 'timedatectl status' },
96
+ { id: 'svc-4', text: 'Disable Avahi/mDNS if not needed', priority: 'recommended', command: 'systemctl is-active avahi-daemon 2>/dev/null || echo "not installed"' },
97
+ { id: 'svc-5', text: 'Disable Bluetooth if not needed', priority: 'recommended', command: 'systemctl is-active bluetooth 2>/dev/null || echo "not installed"' },
98
+ ],
99
+ },
100
+ {
101
+ name: 'Web Server (if applicable)', icon: 'πŸ•ΈοΈ',
102
+ items: [
103
+ { id: 'web-1', text: 'Hide server version headers', priority: 'critical', command: 'curl -sI localhost | grep -i server' },
104
+ { id: 'web-2', text: 'Enable HTTPS with strong TLS (1.2+)', priority: 'critical', command: 'openssl s_client -connect localhost:443 -brief 2>/dev/null | head -5' },
105
+ { id: 'web-3', text: 'Set security headers (HSTS, CSP, X-Frame-Options)', priority: 'recommended', command: 'curl -sI localhost | grep -iE "strict|content-security|x-frame"' },
106
+ { id: 'web-4', text: 'Disable directory listing', priority: 'critical', command: 'curl -s localhost/nonexistent/ | head -3' },
107
+ { id: 'web-5', text: 'Configure rate limiting', priority: 'recommended', command: 'echo "Check nginx/apache config for rate limiting directives"' },
108
+ ],
109
+ },
110
+ ];
111
+
112
+ function ask(rl, q) { return new Promise(r => rl.question(q, a => r(a.trim()))); }
113
+
114
+ function priorityLabel(p) {
115
+ if (p === 'critical') return `${c.bgRed}${c.white}${c.bold} CRITICAL ${c.reset}`;
116
+ if (p === 'recommended') return `${c.bgYellow}${c.bold} RECOMMENDED ${c.reset}`;
117
+ return `${c.dim} OPTIONAL ${c.reset}`;
118
+ }
119
+
120
+ async function main() {
121
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
122
+ const checked = {};
123
+
124
+ console.log(`\n${c.bold}${c.cyan} ╔══════════════════════════════════════════════════╗`);
125
+ console.log(` β•‘${c.white} πŸ›‘οΈ SERVER HARDENING CHECKLIST πŸ”’ ${c.cyan}β•‘`);
126
+ console.log(` β•‘${c.dim}${c.white} Interactive Linux security audit ${c.cyan}β•‘`);
127
+ console.log(` β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${c.reset}`);
128
+ console.log(`${c.dim} Powered by dargslan.com β€” 210+ IT eBooks${c.reset}\n`);
129
+
130
+ console.log(` ${c.green}[1]${c.reset} Full Audit (all sections)`);
131
+ console.log(` ${c.yellow}[2]${c.reset} Critical Items Only`);
132
+ console.log(` ${c.cyan}[3]${c.reset} Choose Section`);
133
+ console.log(` ${c.magenta}[4]${c.reset} View Commands Only (no interaction)\n`);
134
+
135
+ const mode = parseInt(await ask(rl, ` ${c.bold}Choice [1-4]: ${c.reset}`)) || 1;
136
+
137
+ let sections;
138
+ if (mode === 3) {
139
+ console.log('');
140
+ SECTIONS.forEach((s, i) => console.log(` [${i + 1}] ${s.icon} ${s.name} (${s.items.length} items)`));
141
+ const si = (parseInt(await ask(rl, `\n ${c.bold}Section [1-${SECTIONS.length}]: ${c.reset}`)) || 1) - 1;
142
+ sections = [SECTIONS[Math.min(si, SECTIONS.length - 1)]];
143
+ } else {
144
+ sections = SECTIONS;
145
+ }
146
+
147
+ let totalItems = 0;
148
+ let completedItems = 0;
149
+ let criticalMissed = 0;
150
+
151
+ for (const section of sections) {
152
+ console.log(`\n${c.bold}${c.cyan} ════ ${section.icon} ${section.name.toUpperCase()} ════${c.reset}\n`);
153
+
154
+ const items = mode === 2 ? section.items.filter(i => i.priority === 'critical') : section.items;
155
+
156
+ for (const item of items) {
157
+ totalItems++;
158
+ console.log(` ${priorityLabel(item.priority)} ${c.bold}${item.text}${c.reset}`);
159
+
160
+ if (item.config) {
161
+ console.log(` ${c.dim}Config: ${item.config}${c.reset}`);
162
+ }
163
+ console.log(` ${c.dim}Check: ${item.command}${c.reset}`);
164
+
165
+ if (mode === 4) {
166
+ console.log('');
167
+ continue;
168
+ }
169
+
170
+ const answer = await ask(rl, ` ${c.bold}Done? [y/n/s(kip)]: ${c.reset}`);
171
+ const lower = answer.toLowerCase();
172
+
173
+ if (lower === 'y' || lower === 'yes') {
174
+ checked[item.id] = true;
175
+ completedItems++;
176
+ console.log(` ${c.green}βœ“ Checked off${c.reset}\n`);
177
+ } else if (lower === 's' || lower === 'skip') {
178
+ console.log(` ${c.yellow}β†’ Skipped${c.reset}\n`);
179
+ } else {
180
+ if (item.priority === 'critical') criticalMissed++;
181
+ console.log(` ${c.red}βœ— Not done β€” ${item.priority === 'critical' ? 'ACTION REQUIRED' : 'consider implementing'}${c.reset}\n`);
182
+ }
183
+ }
184
+ }
185
+
186
+ if (mode !== 4) {
187
+ const pct = totalItems > 0 ? Math.round(completedItems / totalItems * 100) : 0;
188
+ const sc = pct >= 80 ? c.green : pct >= 60 ? c.yellow : c.red;
189
+
190
+ console.log(`\n${c.bold}${c.cyan} ═══════════ AUDIT RESULTS ═══════════${c.reset}\n`);
191
+ console.log(` Completed: ${sc}${c.bold}${completedItems}/${totalItems} (${pct}%)${c.reset}`);
192
+
193
+ if (criticalMissed > 0) {
194
+ console.log(` ${c.bgRed}${c.white}${c.bold} ⚠ ${criticalMissed} CRITICAL items not completed! ${c.reset}`);
195
+ } else {
196
+ console.log(` ${c.bgGreen}${c.white}${c.bold} βœ“ All critical items addressed ${c.reset}`);
197
+ }
198
+
199
+ const grade = pct >= 90 ? 'A' : pct >= 80 ? 'B' : pct >= 70 ? 'C' : pct >= 60 ? 'D' : 'F';
200
+ const gc = grade <= 'B' ? c.green : grade <= 'C' ? c.yellow : c.red;
201
+ console.log(`\n Security Grade: ${gc}${c.bold}${grade}${c.reset}`);
202
+
203
+ if (pct < 80) {
204
+ console.log(`\n ${c.bold}Recommended reading:${c.reset}`);
205
+ console.log(` πŸ“– Linux System Hardening\n ${c.cyan}https://dargslan.com/book/linux-system-hardening${c.reset}`);
206
+ console.log(` πŸ“– Linux Security Essentials\n ${c.cyan}https://dargslan.com/book/linux-security-essentials${c.reset}`);
207
+ } else {
208
+ console.log(`\n ${c.bold}Level up your skills:${c.reset}`);
209
+ console.log(` πŸ“– Ethical Hacking\n ${c.cyan}https://dargslan.com/book/ethical-hacking${c.reset}`);
210
+ console.log(` πŸ“– Linux Performance Tuning\n ${c.cyan}https://dargslan.com/book/linux-performance-tuning${c.reset}`);
211
+ }
212
+ }
213
+
214
+ console.log(`\n πŸ“š 210+ IT eBooks: ${c.cyan}https://dargslan.com/books${c.reset}`);
215
+ console.log(` πŸ“„ 260+ Cheat Sheets: ${c.cyan}https://dargslan.com/cheat-sheets${c.reset}`);
216
+ console.log(` πŸ“ IT Blog: ${c.cyan}https://dargslan.com/blog${c.reset}`);
217
+ console.log(` πŸ”’ Security Quiz: ${c.cyan}npx dargslan-security-quiz${c.reset}`);
218
+ console.log(`\n${c.dim} Made with ❀ by Dargslan β€” dargslan.com${c.reset}\n`);
219
+
220
+ rl.close();
221
+ }
222
+
223
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "server-hardening-checklist",
3
+ "version": "1.0.0",
4
+ "description": "Interactive Linux server security hardening checklist in your terminal. 50+ security checks for SSH, firewall, users, updates, and more.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "server-hardening-checklist": "./index.js",
8
+ "harden": "./index.js"
9
+ },
10
+ "keywords": [
11
+ "security", "hardening", "server", "linux", "checklist", "terminal", "cli",
12
+ "ssh", "firewall", "fail2ban", "audit", "compliance", "cis-benchmark",
13
+ "sysadmin", "devops", "best-practices", "pentest"
14
+ ],
15
+ "author": "Dargslan <info@dargslan.com> (https://dargslan.com)",
16
+ "license": "MIT",
17
+ "homepage": "https://dargslan.com",
18
+ "repository": { "type": "git", "url": "https://github.com/Dargslan/server-hardening-checklist" },
19
+ "engines": { "node": ">=14.0.0" },
20
+ "files": ["index.js", "README.md", "LICENSE"]
21
+ }