@misterhuydo/sentinel 1.0.26 → 1.0.28

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/.cairn/.hint-lock CHANGED
@@ -1 +1 @@
1
- 2026-03-21T19:49:49.891Z
1
+ 2026-03-21T20:21:02.908Z
@@ -1,6 +1,6 @@
1
1
  {
2
- "message": "Auto-checkpoint at 2026-03-21T20:00:24.443Z",
3
- "checkpoint_at": "2026-03-21T20:00:24.444Z",
2
+ "message": "Auto-checkpoint at 2026-03-21T20:27:04.713Z",
3
+ "checkpoint_at": "2026-03-21T20:27:04.714Z",
4
4
  "active_files": [],
5
5
  "notes": [],
6
6
  "mtime_snapshot": {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/sentinel",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Sentinel — Autonomous DevOps Agent installer and manager",
5
5
  "bin": {
6
6
  "sentinel": "./bin/sentinel.js"
@@ -0,0 +1,201 @@
1
+ #!/bin/bash
2
+ # fetch_log.sh — Generic SSH log fetcher
3
+ #
4
+ # Usage:
5
+ # ./fetch_log.sh # reads all *.properties in script dir
6
+ # ./fetch_log.sh SSOLWA.properties # specific file(s)
7
+ # ./fetch_log.sh SSOLWA.properties UAS.properties
8
+ # ./fetch_log.sh /path/to/SSOLWA.properties, /path/to/UAS.properties
9
+ #
10
+ # Properties file format:
11
+ # KEY=/path/to/ssh.pem (required)
12
+ # HOSTS=host1, ec2-user@host2, host3 (required, default user: ec2-user)
13
+ # LOGS=logs/app.log, logs/alarm.log (required, backslash or forward slash)
14
+ # REMOTE_SERVICE_USER=MyService (optional, defaults to properties filename)
15
+ # OUTPUT_DIR=/path/to/output (optional, defaults to script dir)
16
+ # TAIL=200 (optional, tail -n N)
17
+ # HEAD=100 (optional, head -n N; ignored if TAIL set)
18
+ # GREP_FILTER=WARN|ERROR (optional, grep -E filter)
19
+ # GREP_EXCLUDE=SSLTool|CommandValidate (optional, grep -iv exclude)
20
+
21
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
22
+ DEFAULT_SSH_USER="ec2-user"
23
+
24
+ show_help() {
25
+ cat << 'EOF'
26
+ fetch_log.sh — Generic SSH log fetcher
27
+
28
+ USAGE
29
+ ./fetch_log.sh Read all *.properties in script dir
30
+ ./fetch_log.sh FILE.properties ... One or more properties files
31
+ ./fetch_log.sh /path/A.properties, /path/B.properties
32
+
33
+ OUTPUT
34
+ OUTPUT_DIR/<ServiceName>/<logname>-<node>.log
35
+ e.g. SSOLWA/INN-SSOLWA-1.log, SSOLWA/INN-SSOLWA-2.log
36
+ UAS/UserAdminService-1.log
37
+
38
+ PROPERTIES FILE FORMAT
39
+ Required:
40
+ KEY=/path/to/ssh.pem
41
+ HOSTS=host1, ec2-user@host2, host3
42
+ LOGS=logs/app.log, logs/alarm.log
43
+
44
+ Optional:
45
+ REMOTE_SERVICE_USER=MyService Defaults to properties filename.
46
+ Log path resolves to /home/<REMOTE_SERVICE_USER>/<LOGS>
47
+ OUTPUT_DIR=/path/to/output Defaults to script directory
48
+ TAIL=200 Fetch last N lines (tail -n N)
49
+ HEAD=100 Fetch first N lines (head -n N); ignored if TAIL set
50
+ GREP_FILTER=WARN|ERROR Keep only matching lines (grep -E)
51
+ GREP_EXCLUDE=SSLTool|hystrix Exclude matching lines (grep -iv)
52
+
53
+ Notes:
54
+ - HOSTS without user@ prefix default to ec2-user@<host>
55
+ - LOGS paths support both backslash and forward slash
56
+ - Lines starting with # are treated as comments
57
+
58
+ EXAMPLE (SSOLWA.properties)
59
+ KEY=/home/huy/.ssh/prod/sso-lwa.pem
60
+ HOSTS=ec2-1.eu-north-1.compute.amazonaws.com, ec2-2.eu-north-1.compute.amazonaws.com
61
+ LOGS=logs/INN-SSOLWA.log, logs/alarm.log
62
+ REMOTE_SERVICE_USER=SSOLoginWebApp
63
+ GREP_FILTER=WARN|ERROR
64
+ GREP_EXCLUDE=SSLTool|CommandValidate
65
+ TAIL=200
66
+ EOF
67
+ }
68
+
69
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
70
+ show_help
71
+ exit 0
72
+ fi
73
+
74
+ trim() {
75
+ local s="$*"
76
+ s="${s#"${s%%[![:space:]]*}"}"
77
+ s="${s%"${s##*[![:space:]]}"}"
78
+ printf '%s' "$s"
79
+ }
80
+
81
+ fetch_from_properties() {
82
+ local props_file="$1"
83
+ local KEY="" HOSTS="" LOGS="" TAIL="" HEAD=""
84
+ local OUTPUT_DIR="" REMOTE_SERVICE_USER="" GREP_FILTER="" GREP_EXCLUDE=""
85
+
86
+ while IFS='=' read -r raw_key raw_val || [[ -n "$raw_key" ]]; do
87
+ local k v
88
+ k="$(trim "$raw_key")"
89
+ v="$(trim "${raw_val:-}")"
90
+ v="${v%%\#*}" # strip inline comments
91
+ v="$(trim "$v")"
92
+ [[ "$k" =~ ^[[:space:]]*# || -z "$k" ]] && continue
93
+ case "$k" in
94
+ KEY) KEY="$v" ;;
95
+ HOSTS) HOSTS="$v" ;;
96
+ LOGS) LOGS="$v" ;;
97
+ TAIL) TAIL="$v" ;;
98
+ HEAD) HEAD="$v" ;;
99
+ OUTPUT_DIR) OUTPUT_DIR="$v" ;;
100
+ REMOTE_SERVICE_USER) REMOTE_SERVICE_USER="$v" ;;
101
+ GREP_FILTER) GREP_FILTER="$v" ;;
102
+ GREP_EXCLUDE) GREP_EXCLUDE="$v" ;;
103
+ esac
104
+ done < "$props_file"
105
+
106
+ if [[ -z "$KEY" || -z "$HOSTS" || -z "$LOGS" ]]; then
107
+ echo "SKIP: $props_file — missing required KEY, HOSTS, or LOGS"
108
+ return 0
109
+ fi
110
+
111
+ local SERVICE_NAME
112
+ SERVICE_NAME="$(basename "$props_file" .properties)"
113
+ REMOTE_SERVICE_USER="${REMOTE_SERVICE_USER:-$SERVICE_NAME}"
114
+ OUTPUT_DIR="${OUTPUT_DIR:-$SCRIPT_DIR}"
115
+
116
+ local SERVICE_DIR="$OUTPUT_DIR/$SERVICE_NAME"
117
+ mkdir -p "$SERVICE_DIR"
118
+
119
+ # Parse comma-separated hosts
120
+ IFS=',' read -ra HOST_ARRAY <<< "$HOSTS"
121
+ # Parse comma-separated log paths
122
+ IFS=',' read -ra LOG_ARRAY <<< "$LOGS"
123
+
124
+ local node_idx=1
125
+ for raw_host in "${HOST_ARRAY[@]}"; do
126
+ local host_entry ssh_user ssh_host
127
+ host_entry="$(trim "$raw_host")"
128
+ [[ -z "$host_entry" ]] && continue
129
+
130
+ if [[ "$host_entry" == *"@"* ]]; then
131
+ ssh_user="${host_entry%%@*}"
132
+ ssh_host="${host_entry##*@}"
133
+ else
134
+ ssh_user="$DEFAULT_SSH_USER"
135
+ ssh_host="$host_entry"
136
+ fi
137
+
138
+ echo "--- [$SERVICE_NAME] node-$node_idx ($ssh_host) ---"
139
+
140
+ for raw_log in "${LOG_ARRAY[@]}"; do
141
+ local log_path
142
+ log_path="$(trim "$raw_log")"
143
+ log_path="${log_path//\\//}" # backslash → forward slash
144
+ log_path="${log_path#/}" # strip leading slash
145
+
146
+ local remote_path="/home/$REMOTE_SERVICE_USER/$log_path"
147
+
148
+ # Build remote command
149
+ local remote_cmd="sudo cat $remote_path 2>/dev/null"
150
+ [[ -n "$GREP_FILTER" ]] && remote_cmd+=" | grep -E '$GREP_FILTER'"
151
+ [[ -n "$GREP_EXCLUDE" ]] && remote_cmd+=" | grep -iv '$GREP_EXCLUDE'"
152
+ [[ -n "$TAIL" ]] && remote_cmd+=" | tail -n $TAIL"
153
+ [[ -n "$HEAD" ]] && [[ -z "$TAIL" ]] && remote_cmd+=" | head -n $HEAD"
154
+
155
+ local log_basename dest
156
+ log_basename="$(basename "$log_path")"
157
+ log_basename="${log_basename%.*}"
158
+ dest="$SERVICE_DIR/${log_basename}-${node_idx}.log"
159
+
160
+ ssh -i "$KEY" -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
161
+ -l "$ssh_user" "$ssh_host" "$remote_cmd" > "$dest" 2>/dev/null \
162
+ && echo " OK: $dest" \
163
+ || echo " FAILED: $log_path on $ssh_host"
164
+ done
165
+
166
+ ((node_idx++))
167
+ done
168
+ }
169
+
170
+ # ── Collect properties files ──────────────────────────────────────────────────
171
+ declare -a PROPS_FILES=()
172
+
173
+ if [[ $# -eq 0 ]]; then
174
+ while IFS= read -r -d '' f; do
175
+ PROPS_FILES+=("$f")
176
+ done < <(find "$SCRIPT_DIR" -maxdepth 1 -name '*.properties' -print0 2>/dev/null | sort -z)
177
+ else
178
+ for arg in "$@"; do
179
+ IFS=',' read -ra parts <<< "$arg"
180
+ for p in "${parts[@]}"; do
181
+ p="$(trim "$p")"
182
+ [[ -n "$p" ]] && PROPS_FILES+=("$p")
183
+ done
184
+ done
185
+ fi
186
+
187
+ if [[ ${#PROPS_FILES[@]} -eq 0 ]]; then
188
+ echo "No .properties files found."
189
+ exit 0
190
+ fi
191
+
192
+ for props in "${PROPS_FILES[@]}"; do
193
+ if [[ ! -f "$props" ]]; then
194
+ echo "SKIP: $props — file not found"
195
+ continue
196
+ fi
197
+ fetch_from_properties "$props"
198
+ echo ""
199
+ done
200
+
201
+ echo "Done."
@@ -115,7 +115,7 @@ class ConfigLoader:
115
115
  # Load workspace-level config first (~/sentinel/sentinel.properties),
116
116
  # then overlay per-project config so project values win.
117
117
  d: dict[str, str] = {}
118
- workspace_props = self.config_dir.parent.parent / "sentinel.properties"
118
+ workspace_props = self.config_dir.resolve().parent.parent / "sentinel.properties"
119
119
  if workspace_props.exists():
120
120
  d.update(_parse_properties(str(workspace_props)))
121
121
  logger.debug("Loaded workspace config from %s", workspace_props)
@@ -159,6 +159,8 @@ class ConfigLoader:
159
159
  return
160
160
  self.log_sources = {}
161
161
  for path in sorted(sources_dir.glob("*.properties")):
162
+ if path.name.startswith("_"):
163
+ continue
162
164
  d = _parse_properties(str(path))
163
165
  s = LogSourceConfig()
164
166
  s.name = path.stem
@@ -181,6 +183,8 @@ class ConfigLoader:
181
183
  return
182
184
  self.repos = {}
183
185
  for path in sorted(repos_dir.glob("*.properties")):
186
+ if path.name.startswith("_"):
187
+ continue
184
188
  d = _parse_properties(str(path))
185
189
  r = RepoConfig()
186
190
  r.repo_name = path.stem