diff --git a/README.md b/README.md index 9951bbe..ac385df 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,13 @@ This plugin introduces new placeholders that can be used within Caddy configurat | `{extra.loadavg.5}` | System load average over the last 5 minutes. | | `{extra.loadavg.15}` | System load average over the last 15 minutes. | | `{extra.hostinfo.uptime}` | System uptime in a human-readable format. | + +### Current Server Local Time Placeholders + +These placeholders reflect the **server's local timezone**: + +| Placeholder | Description | +|--------------------------------------|-------------------------------------------------------| | `{extra.time.now.month}` | Current month as an integer (e.g., 5 for May). | | `{extra.time.now.month_padded}` | Current month as a zero-padded string (e.g., "05" for May). | | `{extra.time.now.day}` | Current day of the month as an integer. | @@ -33,7 +40,31 @@ This plugin introduces new placeholders that can be used within Caddy configurat | `{extra.time.now.iso_week}` | The current ISO week number of the year. | | `{extra.time.now.iso_year}` | The ISO year corresponding to the current ISO week. | | `{extra.time.now.custom}` | Current time in a custom format, configurable via the `time_format_custom` directive. | -| | Note: All `extra.time.now.*` placeholders refer to the system's local timezone. | + +### Current UTC Time Placeholders + +These placeholders reflect **UTC time**: + +| Placeholder | Description | +|--------------------------------------|-------------------------------------------------------| +| `{extra.time.now.utc.month}` | Current month in UTC as an integer (e.g., 5 for May). | +| `{extra.time.now.utc.month_padded}` | Current month in UTC as a zero-padded string (e.g., "05" for May). | +| `{extra.time.now.utc.day}` | Current day of the month in UTC as an integer. | +| `{extra.time.now.utc.day_padded}` | Current day of the month in UTC as a zero-padded string. | +| `{extra.time.now.utc.hour}` | Current hour in UTC in 24-hour format as an integer. | +| `{extra.time.now.utc.hour_padded}` | Current hour in UTC in 24-hour format as a zero-padded string. | +| `{extra.time.now.utc.minute}` | Current minute in UTC as an integer. | +| `{extra.time.now.utc.minute_padded}` | Current minute in UTC as a zero-padded string. | +| `{extra.time.now.utc.second}` | Current second in UTC as an integer. | +| `{extra.time.now.utc.second_padded}` | Current second in UTC as a zero-padded string. | +| `{extra.time.now.utc.timezone_offset}` | UTC timezone offset (always +0000). | +| `{extra.time.now.utc.timezone_name}` | UTC timezone abbreviation (always UTC). | +| `{extra.time.now.utc.iso_week}` | Current ISO week number of the year in UTC. | +| `{extra.time.now.utc.iso_year}` | ISO year corresponding to the current ISO week in UTC. | +| `{extra.time.now.utc.custom}` | Current UTC time in a custom format, configurable via the `time_format_custom` directive. | + +> [!NOTE] +> All `extra.time.now.*` placeholders refer to the system's local timezone, while `extra.time.now.utc.*` placeholders represent the same values in UTC. ## Building @@ -84,7 +115,7 @@ This means that `{extra.rand.int}` will default to generating a random integer b ### Custom Time Format -The `{extra.time.now.custom}` placeholder can be configured using the `time_format_custom` subdirective inside the `extra_placeholders` directive. +The `{extra.time.now.custom}` and `{extra.time.now.utc.custom}` placeholders can be configured using the `time_format_custom` subdirective inside the `extra_placeholders` directive. This allows you to specify a custom date and time format using [Go's time format syntax](https://pkg.go.dev/time#pkg-constants). ```caddyfile @@ -94,7 +125,7 @@ extra_placeholders { } ``` -If `time_format_custom` is not specified, it defaults to `"2006-01-02 15:04:05"`. This format will be used whenever `{extra.time.now.custom}` is referenced. +If `time_format_custom` is not specified, it defaults to `"2006-01-02 15:04:05"`. This format will be applied to both `{extra.time.now.custom}` (server’s local timezone) and `{extra.time.now.utc.custom}` (UTC time) placeholders. ### Example: Conditional Redirect Based on Random Value diff --git a/extra_placeholders.go b/extra_placeholders.go index 29f6c71..1a73975 100644 --- a/extra_placeholders.go +++ b/extra_placeholders.go @@ -16,14 +16,11 @@ package extraplaceholders import ( "fmt" - "math/rand" "net/http" "time" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" - "github.com/shirou/gopsutil/v4/host" - "github.com/shirou/gopsutil/v4/load" "go.uber.org/zap" ) @@ -39,6 +36,10 @@ import ( // `{extra.loadavg.5}` | System load average over the last 5 minutes. // `{extra.loadavg.15}` | System load average over the last 15 minutes. // `{extra.hostinfo.uptime}` | System uptime in a human-readable format. +// +// Current local time placeholders: +// Placeholder | Description +// ------------|------------- // `{extra.time.now.month}` | Current month as an integer (e.g., 10 for October). // `{extra.time.now.month_padded}` | Current month as a zero-padded string (e.g., "05" for May). // `{extra.time.now.day}` | Current day of the month as an integer. @@ -54,6 +55,25 @@ import ( // `{extra.time.now.iso_week}` | Current ISO week number of the year. // `{extra.time.now.iso_year}` | ISO year corresponding to the current ISO week. // `{extra.time.now.custom}` | Current time in a custom format, configurable via the `time_format_custom` directive. +// +// UTC equivalents of the current time placeholders (with `.utc` added): +// Placeholder | Description +// ------------|------------- +// `{extra.time.now.utc.month}` | Current month in UTC as an integer (e.g., 10 for October). +// `{extra.time.now.utc.month_padded}` | Current month in UTC as a zero-padded string (e.g., "05" for May). +// `{extra.time.now.utc.day}` | Current day of the month in UTC as an integer. +// `{extra.time.now.utc.day_padded}` | Current day of the month in UTC as a zero-padded string. +// `{extra.time.now.utc.hour}` | Current hour in UTC in 24-hour format as an integer. +// `{extra.time.now.utc.hour_padded}` | Current hour in UTC in 24-hour format as a zero-padded string. +// `{extra.time.now.utc.minute}` | Current minute in UTC as an integer. +// `{extra.time.now.utc.minute_padded}` | Current minute in UTC as a zero-padded string. +// `{extra.time.now.utc.second}` | Current second in UTC as an integer. +// `{extra.time.now.utc.second_padded}` | Current second in UTC as a zero-padded string. +// `{extra.time.now.utc.timezone_offset}` | UTC timezone offset (always +0000). +// `{extra.time.now.utc.timezone_name}` | UTC timezone abbreviation (always UTC). +// `{extra.time.now.utc.iso_week}` | Current ISO week number of the year in UTC. +// `{extra.time.now.utc.iso_year}` | ISO year corresponding to the current ISO week in UTC. +// `{extra.time.now.utc.custom}` | Current UTC time in a custom format, configurable via the `time_format_custom` directive. type ExtraPlaceholders struct { // RandIntMin defines the minimum value (inclusive) for the `{extra.rand.int}` placeholder. RandIntMin int `json:"rand_int_min,omitempty"` @@ -61,7 +81,7 @@ type ExtraPlaceholders struct { // RandIntMax defines the maximum value (inclusive) for the `{extra.rand.int}` placeholder. RandIntMax int `json:"rand_int_max,omitempty"` - // TimeFormatCustom specifies a custom time format for the `{extra.time.now.custom}` placeholder. + // TimeFormatCustom specifies a custom time format for the `{extra.time.now.custom}` and `{extra.time.now.utc.custom}` placeholder. // If left empty, a default format of "2006-01-02 15:04:05" is used. TimeFormatCustom string `json:"time_format_custom,omitempty"` @@ -116,60 +136,16 @@ func (e ExtraPlaceholders) ServeHTTP(w http.ResponseWriter, r *http.Request, nex return caddyhttp.Error(http.StatusInternalServerError, nil) } - // Set Caddy version placeholders. - simpleVersion, fullVersion := caddy.Version() - repl.Set("extra.caddy.version.simple", simpleVersion) - repl.Set("extra.caddy.version.full", fullVersion) - - // Set placeholders for random float and integer values. - repl.Set("extra.rand.float", rand.Float64()) - if e.RandIntMax > e.RandIntMin { - repl.Set("extra.rand.int", rand.Intn(e.RandIntMax-e.RandIntMin+1)+e.RandIntMin) - } else { - repl.Set("extra.rand.int", rand.Intn(101)) // Default range 0-100 if not properly configured - } + e.setCaddyPlaceholders(repl) + e.setRandPlaceholders(repl) + e.setLoadavgPlaceholders(repl) + e.setHostinfoPlaceholders(repl) - // Set placeholders for system load averages (1, 5, and 15 minutes). - loadAvg, err := load.Avg() - if err == nil { - repl.Set("extra.loadavg.1", loadAvg.Load1) - repl.Set("extra.loadavg.5", loadAvg.Load5) - repl.Set("extra.loadavg.15", loadAvg.Load15) - } - - // Set placeholder for system uptime. - uptime, err := host.Uptime() - if err == nil { - uptimeDuration := time.Duration(uptime) * time.Second - repl.Set("extra.hostinfo.uptime", uptimeDuration.String()) - } else { - repl.Set("extra.hostinfo.uptime", "error retrieving uptime") - } + // Set time placeholders for server's local time + e.setTimePlaceholders(repl, time.Now(), false) - // Set placeholders for current time (month, day, hour, minute, second). - now := time.Now() // System's local timezone - repl.Set("extra.time.now.month", int(now.Month())) - repl.Set("extra.time.now.month_padded", fmt.Sprintf("%02d", now.Month())) - repl.Set("extra.time.now.day", now.Day()) - repl.Set("extra.time.now.day_padded", fmt.Sprintf("%02d", now.Day())) - repl.Set("extra.time.now.hour", now.Hour()) - repl.Set("extra.time.now.hour_padded", fmt.Sprintf("%02d", now.Hour())) - repl.Set("extra.time.now.minute", now.Minute()) - repl.Set("extra.time.now.minute_padded", fmt.Sprintf("%02d", now.Minute())) - repl.Set("extra.time.now.second", now.Second()) - repl.Set("extra.time.now.second_padded", fmt.Sprintf("%02d", now.Second())) - - // Set placeholders for timezone offset and name. - repl.Set("extra.time.now.timezone_offset", now.Format("-0700")) - repl.Set("extra.time.now.timezone_name", now.Format("MST")) - - // Set placeholders for ISO week and ISO year. - isoYear, isoWeek := now.ISOWeek() - repl.Set("extra.time.now.iso_week", isoWeek) - repl.Set("extra.time.now.iso_year", isoYear) - - // Set custom time format placeholder - repl.Set("extra.time.now.custom", now.Format(e.TimeFormatCustom)) + // Set time placeholders for UTC time + e.setTimePlaceholders(repl, time.Now().UTC(), true) // Call the next handler in the chain. return next.ServeHTTP(w, r) diff --git a/placeholder_caddy.go b/placeholder_caddy.go new file mode 100644 index 0000000..3929d6c --- /dev/null +++ b/placeholder_caddy.go @@ -0,0 +1,26 @@ +// Copyright 2024 Steffen Busch + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extraplaceholders + +import ( + "github.com/caddyserver/caddy/v2" +) + +// setCaddyPlaceholders sets placeholders for the Caddy version. +func (e ExtraPlaceholders) setCaddyPlaceholders(repl *caddy.Replacer) { + simpleVersion, fullVersion := caddy.Version() + repl.Set("extra.caddy.version.simple", simpleVersion) + repl.Set("extra.caddy.version.full", fullVersion) +} diff --git a/placeholder_hostinfo.go b/placeholder_hostinfo.go new file mode 100644 index 0000000..33bb047 --- /dev/null +++ b/placeholder_hostinfo.go @@ -0,0 +1,33 @@ +// Copyright 2024 Steffen Busch + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extraplaceholders + +import ( + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/shirou/gopsutil/v4/host" +) + +// setHostinfoPlaceholders sets placeholders for system uptime in a human-readable format. +func (e ExtraPlaceholders) setHostinfoPlaceholders(repl *caddy.Replacer) { + uptime, err := host.Uptime() + if err == nil { + uptimeDuration := time.Duration(uptime) * time.Second + repl.Set("extra.hostinfo.uptime", uptimeDuration.String()) + } else { + repl.Set("extra.hostinfo.uptime", "error retrieving uptime") + } +} diff --git a/placeholder_loadavg.go b/placeholder_loadavg.go new file mode 100644 index 0000000..134249e --- /dev/null +++ b/placeholder_loadavg.go @@ -0,0 +1,30 @@ +// Copyright 2024 Steffen Busch + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extraplaceholders + +import ( + "github.com/caddyserver/caddy/v2" + "github.com/shirou/gopsutil/v4/load" +) + +// setLoadavgPlaceholders sets placeholders for system load averages (1, 5, and 15 minutes). +func (e ExtraPlaceholders) setLoadavgPlaceholders(repl *caddy.Replacer) { + loadAvg, err := load.Avg() + if err == nil { + repl.Set("extra.loadavg.1", loadAvg.Load1) + repl.Set("extra.loadavg.5", loadAvg.Load5) + repl.Set("extra.loadavg.15", loadAvg.Load15) + } +} diff --git a/placeholder_rand.go b/placeholder_rand.go new file mode 100644 index 0000000..b9b607b --- /dev/null +++ b/placeholder_rand.go @@ -0,0 +1,31 @@ +// Copyright 2024 Steffen Busch + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extraplaceholders + +import ( + "math/rand" + + "github.com/caddyserver/caddy/v2" +) + +// setRandPlaceholders sets placeholders for random float and integer values. +func (e ExtraPlaceholders) setRandPlaceholders(repl *caddy.Replacer) { + repl.Set("extra.rand.float", rand.Float64()) + if e.RandIntMax > e.RandIntMin { + repl.Set("extra.rand.int", rand.Intn(e.RandIntMax-e.RandIntMin+1)+e.RandIntMin) + } else { + repl.Set("extra.rand.int", rand.Intn(101)) // Default range 0-100 if not properly configured + } +} diff --git a/placeholder_time.go b/placeholder_time.go new file mode 100644 index 0000000..07e8196 --- /dev/null +++ b/placeholder_time.go @@ -0,0 +1,42 @@ +package extraplaceholders + +import ( + "fmt" + "time" + + "github.com/caddyserver/caddy/v2" +) + +// setTimePlaceholders sets placeholders for date, time, and custom format, +// using the provided time.Time. If isUTC is true, ".utc" is added in the placeholder path. +func (e ExtraPlaceholders) setTimePlaceholders(repl *caddy.Replacer, t time.Time, isUTC bool) { + // Determine the base path, with or without ".utc" + base := "extra.time.now" + if isUTC { + base += ".utc" + } + + // Set date and time components with the specified base path + repl.Set(fmt.Sprintf("%s.month", base), int(t.Month())) + repl.Set(fmt.Sprintf("%s.month_padded", base), fmt.Sprintf("%02d", t.Month())) + repl.Set(fmt.Sprintf("%s.day", base), t.Day()) + repl.Set(fmt.Sprintf("%s.day_padded", base), fmt.Sprintf("%02d", t.Day())) + repl.Set(fmt.Sprintf("%s.hour", base), t.Hour()) + repl.Set(fmt.Sprintf("%s.hour_padded", base), fmt.Sprintf("%02d", t.Hour())) + repl.Set(fmt.Sprintf("%s.minute", base), t.Minute()) + repl.Set(fmt.Sprintf("%s.minute_padded", base), fmt.Sprintf("%02d", t.Minute())) + repl.Set(fmt.Sprintf("%s.second", base), t.Second()) + repl.Set(fmt.Sprintf("%s.second_padded", base), fmt.Sprintf("%02d", t.Second())) + + // Set timezone offset and name + repl.Set(fmt.Sprintf("%s.timezone_offset", base), t.Format("-0700")) + repl.Set(fmt.Sprintf("%s.timezone_name", base), t.Format("MST")) + + // Set ISO week and year components + isoYear, isoWeek := t.ISOWeek() + repl.Set(fmt.Sprintf("%s.iso_week", base), isoWeek) + repl.Set(fmt.Sprintf("%s.iso_year", base), isoYear) + + // Set custom time format placeholder + repl.Set(fmt.Sprintf("%s.custom", base), t.Format(e.TimeFormatCustom)) +}