commit d7c720a
Artur Manuel
·
2026-04-20 02:33:25 +0000 UTC
parent d7c720a
initialise repository
14 files changed,
+784,
-0
+3,
-0
1@@ -0,0 +1,3 @@
2+[*.go]
3+indent_size = 8
4+indent_style = tab
A
LICENSE
+287,
-0
1@@ -0,0 +1,287 @@
2+ EUROPEAN UNION PUBLIC LICENCE v. 1.2
3+ EUPL © the European Union 2007, 2016
4+
5+This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined
6+below) which is provided under the terms of this Licence. Any use of the Work,
7+other than as authorised under this Licence is prohibited (to the extent such
8+use is covered by a right of the copyright holder of the Work).
9+
10+The Work is provided under the terms of this Licence when the Licensor (as
11+defined below) has placed the following notice immediately following the
12+copyright notice for the Work:
13+
14+ Licensed under the EUPL
15+
16+or has expressed by any other means his willingness to license under the EUPL.
17+
18+1. Definitions
19+
20+In this Licence, the following terms have the following meaning:
21+
22+- ‘The Licence’: this Licence.
23+
24+- ‘The Original Work’: the work or software distributed or communicated by the
25+ Licensor under this Licence, available as Source Code and also as Executable
26+ Code as the case may be.
27+
28+- ‘Derivative Works’: the works or software that could be created by the
29+ Licensee, based upon the Original Work or modifications thereof. This Licence
30+ does not define the extent of modification or dependence on the Original Work
31+ required in order to classify a work as a Derivative Work; this extent is
32+ determined by copyright law applicable in the country mentioned in Article 15.
33+
34+- ‘The Work’: the Original Work or its Derivative Works.
35+
36+- ‘The Source Code’: the human-readable form of the Work which is the most
37+ convenient for people to study and modify.
38+
39+- ‘The Executable Code’: any code which has generally been compiled and which is
40+ meant to be interpreted by a computer as a program.
41+
42+- ‘The Licensor’: the natural or legal person that distributes or communicates
43+ the Work under the Licence.
44+
45+- ‘Contributor(s)’: any natural or legal person who modifies the Work under the
46+ Licence, or otherwise contributes to the creation of a Derivative Work.
47+
48+- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
49+ the Work under the terms of the Licence.
50+
51+- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
52+ renting, distributing, communicating, transmitting, or otherwise making
53+ available, online or offline, copies of the Work or providing access to its
54+ essential functionalities at the disposal of any other natural or legal
55+ person.
56+
57+2. Scope of the rights granted by the Licence
58+
59+The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
60+sublicensable licence to do the following, for the duration of copyright vested
61+in the Original Work:
62+
63+- use the Work in any circumstance and for all usage,
64+- reproduce the Work,
65+- modify the Work, and make Derivative Works based upon the Work,
66+- communicate to the public, including the right to make available or display
67+ the Work or copies thereof to the public and perform publicly, as the case may
68+ be, the Work,
69+- distribute the Work or copies thereof,
70+- lend and rent the Work or copies thereof,
71+- sublicense rights in the Work or copies thereof.
72+
73+Those rights can be exercised on any media, supports and formats, whether now
74+known or later invented, as far as the applicable law permits so.
75+
76+In the countries where moral rights apply, the Licensor waives his right to
77+exercise his moral right to the extent allowed by law in order to make effective
78+the licence of the economic rights here above listed.
79+
80+The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
81+any patents held by the Licensor, to the extent necessary to make use of the
82+rights granted on the Work under this Licence.
83+
84+3. Communication of the Source Code
85+
86+The Licensor may provide the Work either in its Source Code form, or as
87+Executable Code. If the Work is provided as Executable Code, the Licensor
88+provides in addition a machine-readable copy of the Source Code of the Work
89+along with each copy of the Work that the Licensor distributes or indicates, in
90+a notice following the copyright notice attached to the Work, a repository where
91+the Source Code is easily and freely accessible for as long as the Licensor
92+continues to distribute or communicate the Work.
93+
94+4. Limitations on copyright
95+
96+Nothing in this Licence is intended to deprive the Licensee of the benefits from
97+any exception or limitation to the exclusive rights of the rights owners in the
98+Work, of the exhaustion of those rights or of other applicable limitations
99+thereto.
100+
101+5. Obligations of the Licensee
102+
103+The grant of the rights mentioned above is subject to some restrictions and
104+obligations imposed on the Licensee. Those obligations are the following:
105+
106+Attribution right: The Licensee shall keep intact all copyright, patent or
107+trademarks notices and all notices that refer to the Licence and to the
108+disclaimer of warranties. The Licensee must include a copy of such notices and a
109+copy of the Licence with every copy of the Work he/she distributes or
110+communicates. The Licensee must cause any Derivative Work to carry prominent
111+notices stating that the Work has been modified and the date of modification.
112+
113+Copyleft clause: If the Licensee distributes or communicates copies of the
114+Original Works or Derivative Works, this Distribution or Communication will be
115+done under the terms of this Licence or of a later version of this Licence
116+unless the Original Work is expressly distributed only under this version of the
117+Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
118+(becoming Licensor) cannot offer or impose any additional terms or conditions on
119+the Work or Derivative Work that alter or restrict the terms of the Licence.
120+
121+Compatibility clause: If the Licensee Distributes or Communicates Derivative
122+Works or copies thereof based upon both the Work and another work licensed under
123+a Compatible Licence, this Distribution or Communication can be done under the
124+terms of this Compatible Licence. For the sake of this clause, ‘Compatible
125+Licence’ refers to the licences listed in the appendix attached to this Licence.
126+Should the Licensee's obligations under the Compatible Licence conflict with
127+his/her obligations under this Licence, the obligations of the Compatible
128+Licence shall prevail.
129+
130+Provision of Source Code: When distributing or communicating copies of the Work,
131+the Licensee will provide a machine-readable copy of the Source Code or indicate
132+a repository where this Source will be easily and freely available for as long
133+as the Licensee continues to distribute or communicate the Work.
134+
135+Legal Protection: This Licence does not grant permission to use the trade names,
136+trademarks, service marks, or names of the Licensor, except as required for
137+reasonable and customary use in describing the origin of the Work and
138+reproducing the content of the copyright notice.
139+
140+6. Chain of Authorship
141+
142+The original Licensor warrants that the copyright in the Original Work granted
143+hereunder is owned by him/her or licensed to him/her and that he/she has the
144+power and authority to grant the Licence.
145+
146+Each Contributor warrants that the copyright in the modifications he/she brings
147+to the Work are owned by him/her or licensed to him/her and that he/she has the
148+power and authority to grant the Licence.
149+
150+Each time You accept the Licence, the original Licensor and subsequent
151+Contributors grant You a licence to their contributions to the Work, under the
152+terms of this Licence.
153+
154+7. Disclaimer of Warranty
155+
156+The Work is a work in progress, which is continuously improved by numerous
157+Contributors. It is not a finished work and may therefore contain defects or
158+‘bugs’ inherent to this type of development.
159+
160+For the above reason, the Work is provided under the Licence on an ‘as is’ basis
161+and without warranties of any kind concerning the Work, including without
162+limitation merchantability, fitness for a particular purpose, absence of defects
163+or errors, accuracy, non-infringement of intellectual property rights other than
164+copyright as stated in Article 6 of this Licence.
165+
166+This disclaimer of warranty is an essential part of the Licence and a condition
167+for the grant of any rights to the Work.
168+
169+8. Disclaimer of Liability
170+
171+Except in the cases of wilful misconduct or damages directly caused to natural
172+persons, the Licensor will in no event be liable for any direct or indirect,
173+material or moral, damages of any kind, arising out of the Licence or of the use
174+of the Work, including without limitation, damages for loss of goodwill, work
175+stoppage, computer failure or malfunction, loss of data or any commercial
176+damage, even if the Licensor has been advised of the possibility of such damage.
177+However, the Licensor will be liable under statutory product liability laws as
178+far such laws apply to the Work.
179+
180+9. Additional agreements
181+
182+While distributing the Work, You may choose to conclude an additional agreement,
183+defining obligations or services consistent with this Licence. However, if
184+accepting obligations, You may act only on your own behalf and on your sole
185+responsibility, not on behalf of the original Licensor or any other Contributor,
186+and only if You agree to indemnify, defend, and hold each Contributor harmless
187+for any liability incurred by, or claims asserted against such Contributor by
188+the fact You have accepted any warranty or additional liability.
189+
190+10. Acceptance of the Licence
191+
192+The provisions of this Licence can be accepted by clicking on an icon ‘I agree’
193+placed under the bottom of a window displaying the text of this Licence or by
194+affirming consent in any other similar way, in accordance with the rules of
195+applicable law. Clicking on that icon indicates your clear and irrevocable
196+acceptance of this Licence and all of its terms and conditions.
197+
198+Similarly, you irrevocably accept this Licence and all of its terms and
199+conditions by exercising any rights granted to You by Article 2 of this Licence,
200+such as the use of the Work, the creation by You of a Derivative Work or the
201+Distribution or Communication by You of the Work or copies thereof.
202+
203+11. Information to the public
204+
205+In case of any Distribution or Communication of the Work by means of electronic
206+communication by You (for example, by offering to download the Work from a
207+remote location) the distribution channel or media (for example, a website) must
208+at least provide to the public the information requested by the applicable law
209+regarding the Licensor, the Licence and the way it may be accessible, concluded,
210+stored and reproduced by the Licensee.
211+
212+12. Termination of the Licence
213+
214+The Licence and the rights granted hereunder will terminate automatically upon
215+any breach by the Licensee of the terms of the Licence.
216+
217+Such a termination will not terminate the licences of any person who has
218+received the Work from the Licensee under the Licence, provided such persons
219+remain in full compliance with the Licence.
220+
221+13. Miscellaneous
222+
223+Without prejudice of Article 9 above, the Licence represents the complete
224+agreement between the Parties as to the Work.
225+
226+If any provision of the Licence is invalid or unenforceable under applicable
227+law, this will not affect the validity or enforceability of the Licence as a
228+whole. Such provision will be construed or reformed so as necessary to make it
229+valid and enforceable.
230+
231+The European Commission may publish other linguistic versions or new versions of
232+this Licence or updated versions of the Appendix, so far this is required and
233+reasonable, without reducing the scope of the rights granted by the Licence. New
234+versions of the Licence will be published with a unique version number.
235+
236+All linguistic versions of this Licence, approved by the European Commission,
237+have identical value. Parties can take advantage of the linguistic version of
238+their choice.
239+
240+14. Jurisdiction
241+
242+Without prejudice to specific agreement between parties,
243+
244+- any litigation resulting from the interpretation of this License, arising
245+ between the European Union institutions, bodies, offices or agencies, as a
246+ Licensor, and any Licensee, will be subject to the jurisdiction of the Court
247+ of Justice of the European Union, as laid down in article 272 of the Treaty on
248+ the Functioning of the European Union,
249+
250+- any litigation arising between other parties and resulting from the
251+ interpretation of this License, will be subject to the exclusive jurisdiction
252+ of the competent court where the Licensor resides or conducts its primary
253+ business.
254+
255+15. Applicable Law
256+
257+Without prejudice to specific agreement between parties,
258+
259+- this Licence shall be governed by the law of the European Union Member State
260+ where the Licensor has his seat, resides or has his registered office,
261+
262+- this licence shall be governed by Belgian law if the Licensor has no seat,
263+ residence or registered office inside a European Union Member State.
264+
265+Appendix
266+
267+‘Compatible Licences’ according to Article 5 EUPL are:
268+
269+- GNU General Public License (GPL) v. 2, v. 3
270+- GNU Affero General Public License (AGPL) v. 3
271+- Open Software License (OSL) v. 2.1, v. 3.0
272+- Eclipse Public License (EPL) v. 1.0
273+- CeCILL v. 2.0, v. 2.1
274+- Mozilla Public Licence (MPL) v. 2
275+- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
276+- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
277+ works other than software
278+- European Union Public Licence (EUPL) v. 1.1, v. 1.2
279+- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
280+ Reciprocity (LiLiQ-R+).
281+
282+The European Commission may update this Appendix to later versions of the above
283+licences without producing a new version of the EUPL, as long as they provide
284+the rights granted in Article 2 of this Licence and protect the covered Source
285+Code from exclusive appropriation.
286+
287+All other changes or additions to this Appendix require the production of a new
288+EUPL version.
A
Makefile
+22,
-0
1@@ -0,0 +1,22 @@
2+SHELL = /bin/sh
3+GO ?= go
4+PREFIX ?= /usr/local
5+BINDIR = $(PREFIX)/bin
6+
7+.PHONY: all
8+all:
9+ $(GO) build ./cmd/webfetch
10+
11+.PHONY: install
12+install: all
13+ install -Dm755 ./webfetch $(BINDIR)/webfetch
14+
15+clean:
16+ rm ./webfetch
17+
18+.PHONY: help
19+help:
20+ @echo "Usage:"
21+ @echo "all: make webfetch"
22+ @echo "install: install webfetch"
23+
+28,
-0
1@@ -0,0 +1,28 @@
2+# webfetch
3+
4+Styling CLI programs is a hassle. Let's use our browser!
5+
6+## Why did you make this?
7+
8+In a message that wasn't directed to me, izder456 asked someone if they
9+saw their new project. It was exfetch running via the web. This
10+intrigued me enough to try it myself given that templates where not in
11+use.
12+
13+> Does crystal have a templating thing like Go or other languages
14+
15+> either way now i have the idea, thanks :)
16+
17+Now we are here, a Go program that creates an HTML page with your
18+systems fetch information.
19+
20+## How do I build and install this?
21+
22+`make install` should do the trick, but make sure to specify the PREFIX
23+lest you are fine with installing it to /usr/local.
24+
25+## Where can I find this project?
26+
27+This projects official repository lies on [Codeberg][repo].
28+
29+[repo]: https://codeberg.org/amadaluzia/webfetch "amadaluzia/webfetch on codeberg.org"
+27,
-0
1@@ -0,0 +1,27 @@
2+package main
3+
4+import (
5+ "html/template"
6+ "log"
7+ "os"
8+
9+ "codeberg.org/amadaluzia/webfetch/internal"
10+)
11+
12+func main() {
13+ var sys internal.SystemInfo
14+ if err := sys.Load(); err != nil {
15+ log.Fatalln(err)
16+ }
17+ output, err := os.OpenFile("output.html", os.O_WRONLY|os.O_CREATE, 0644)
18+ if err != nil {
19+ log.Fatalln(err)
20+ }
21+ t, err := template.ParseFiles("webfetch.html")
22+ if err != nil {
23+ log.Fatalln(err)
24+ }
25+ if err := t.Execute(output, sys); err != nil {
26+ log.Fatalln(err)
27+ }
28+}
A
go.mod
+3,
-0
1@@ -0,0 +1,3 @@
2+module codeberg.org/amadaluzia/webfetch
3+
4+go 1.26.1
+33,
-0
1@@ -0,0 +1,33 @@
2+package internal
3+
4+import (
5+ "bufio"
6+ "bytes"
7+ "fmt"
8+ "strings"
9+)
10+
11+type OsRelease map[string]string
12+
13+func (os OsRelease) UnmarshalText(text []byte) error {
14+ var b bytes.Buffer
15+ if _, err := b.Write(text); err != nil {
16+ return fmt.Errorf("failed to write bytes to buffer: %v", err)
17+ }
18+ scanner := bufio.NewScanner(&b)
19+ line := 1
20+ for scanner.Scan() {
21+ pair := strings.SplitN(scanner.Text(), "=", 2)
22+ if len(pair) < 2 {
23+ return fmt.Errorf("failed to parse /etc/os-release: expected = at line %d", line)
24+ }
25+ key, val := pair[0], pair[1]
26+ if val[0] != '"' {
27+ os[key] = val
28+ } else {
29+ os[key] = val[1 : len(val)-1]
30+ }
31+ line++
32+ }
33+ return nil
34+}
+72,
-0
1@@ -0,0 +1,72 @@
2+package internal
3+
4+import (
5+ "bufio"
6+ "bytes"
7+ "fmt"
8+ "slices"
9+ "strconv"
10+)
11+
12+type PasswdEntry struct {
13+ Username string
14+ Password string
15+ UID int
16+ GID int
17+ GECOS string
18+ Home string
19+ Shell string
20+}
21+
22+type Passwd []PasswdEntry
23+
24+func (passwd *PasswdEntry) UnmarshalText(text []byte) error {
25+ line := bytes.Split(text, []byte{':'})
26+ passwd.Username = string(line[0])
27+ passwd.Password = string(line[1])
28+ uid, err := strconv.Atoi(string(line[2]))
29+ if err != nil {
30+ return fmt.Errorf("failed to convert UID to int: %v", err)
31+ }
32+ gid, err := strconv.Atoi(string(line[3]))
33+ if err != nil {
34+ return fmt.Errorf("failed to convert GID to int: %v", err)
35+ }
36+ passwd.UID = uid
37+ passwd.GID = gid
38+ passwd.GECOS = string(line[4])
39+ passwd.Home = string(line[5])
40+ passwd.Shell = string(line[6])
41+ return nil
42+}
43+
44+func (passwd *Passwd) UnmarshalText(text []byte) error {
45+ var b bytes.Buffer
46+ if _, err := b.Write(text); err != nil {
47+ return fmt.Errorf("failed to write passwd file to buffer: %v", err)
48+ }
49+ scanner := bufio.NewScanner(&b)
50+ line := 1
51+ for scanner.Scan() {
52+ var entry PasswdEntry
53+ if err := entry.UnmarshalText(scanner.Bytes()); err != nil {
54+ return fmt.Errorf("failed to parse passwd file, line %d: %v", line, err)
55+ }
56+ *passwd = append(*passwd, entry)
57+ line++
58+ }
59+ return nil
60+}
61+
62+func (passwd Passwd) Entry(user string) (PasswdEntry, error) {
63+ if len(passwd) == 0 {
64+ return PasswdEntry{}, fmt.Errorf("passwd has no entries")
65+ }
66+ i := slices.IndexFunc(passwd, func(pwent PasswdEntry) bool {
67+ return pwent.Username == user
68+ })
69+ if i == -1 {
70+ return PasswdEntry{}, fmt.Errorf("failed to find user %s", user)
71+ }
72+ return passwd[i], nil
73+}
+74,
-0
1@@ -0,0 +1,74 @@
2+package internal
3+
4+import (
5+ "syscall"
6+)
7+
8+type SystemInfo struct {
9+ OS string
10+ Hostname string
11+ Username string
12+ KernelRelease string
13+ KernelName string
14+ MachineArch string
15+ Shell string
16+ Uptime string
17+ Terminal string
18+}
19+
20+func (sysinfo *SystemInfo) Load() error {
21+ hostname, err := GetHostname()
22+ if err != nil {
23+ return err
24+ }
25+ osRelease, err := GetOsRelease()
26+ if err != nil {
27+ return err
28+ }
29+ username, err := GetUsername()
30+ if err != nil {
31+ return err
32+ }
33+ var uname syscall.Utsname
34+ if err := syscall.Uname(&uname); err != nil {
35+ return err
36+ }
37+ kversion, err := GetKernelRelease(uname)
38+ if err != nil {
39+ return err
40+ }
41+ kname, err := GetKernelName(uname)
42+ if err != nil {
43+ return err
44+ }
45+ march, err := GetMachineArch(uname)
46+ if err != nil {
47+ return err
48+ }
49+ passwd, err := GetPasswd()
50+ if err != nil {
51+ return err
52+ }
53+ shell, err := GetShell(username, passwd)
54+ if err != nil {
55+ return err
56+ }
57+ up, err := GetUptime()
58+ if err != nil {
59+ return err
60+ }
61+ terminal, err := GetTerminal()
62+ if err != nil {
63+ return err
64+ }
65+ sysinfo.OS = osRelease["PRETTY_NAME"]
66+ sysinfo.Hostname = hostname
67+ sysinfo.Username = username
68+ sysinfo.KernelRelease = kversion
69+ sysinfo.KernelName = kname
70+ sysinfo.MachineArch = march
71+ sysinfo.Shell = shell
72+ sysinfo.Uptime = up.String()
73+ sysinfo.Terminal = terminal
74+ return nil
75+}
+44,
-0
1@@ -0,0 +1,44 @@
2+package internal
3+
4+import (
5+ "bytes"
6+ "fmt"
7+ "strings"
8+ "syscall"
9+)
10+
11+func cleanNulls(s string) string {
12+ return strings.TrimFunc(s, func(r rune) bool {
13+ return r == 0
14+ })
15+}
16+
17+func GetKernelName(uname syscall.Utsname) (string, error) {
18+ var b bytes.Buffer
19+ for _, v := range uname.Sysname {
20+ if err := b.WriteByte(byte(v)); err != nil {
21+ return "", fmt.Errorf("failed to write kernel name to buffer: %v", err)
22+ }
23+ }
24+ return cleanNulls(b.String()), nil
25+}
26+
27+func GetKernelRelease(uname syscall.Utsname) (string, error) {
28+ var b bytes.Buffer
29+ for _, v := range uname.Release {
30+ if err := b.WriteByte(byte(v)); err != nil {
31+ return "", fmt.Errorf("failed to write kernel release to buffer: %v", err)
32+ }
33+ }
34+ return cleanNulls(b.String()) , nil
35+}
36+
37+func GetMachineArch(uname syscall.Utsname) (string, error) {
38+ var b bytes.Buffer
39+ for _, v := range uname.Machine {
40+ if err := b.WriteByte(byte(v)); err != nil {
41+ return "", fmt.Errorf("failed to write machine to buffer: %v", err)
42+ }
43+ }
44+ return cleanNulls(b.String()) , nil
45+}
+35,
-0
1@@ -0,0 +1,35 @@
2+package internal
3+
4+import (
5+ "bytes"
6+ "fmt"
7+ "math"
8+ "strconv"
9+)
10+
11+type Uptime struct {
12+ Days int
13+ Hours int
14+ Minutes int
15+ Seconds int
16+}
17+
18+func (up *Uptime) UnmarshalText(text []byte) error {
19+ times := bytes.Split(text, []byte{' '})
20+ if len(times) < 2 {
21+ return fmt.Errorf("there must be more than one time")
22+ }
23+ upSeconds, err := strconv.ParseFloat(string(times[0]), 32)
24+ if err != nil {
25+ return fmt.Errorf("failed to parse seconds to float: %v", err)
26+ }
27+ up.Days = int(math.Floor(upSeconds)) / 86400
28+ up.Hours = int(math.Floor(upSeconds)) / 3600 % 24
29+ up.Minutes = int(math.Floor(upSeconds)) / 60 % 60
30+ up.Seconds = int(math.Floor(upSeconds)) % 60
31+ return nil
32+}
33+
34+func (up *Uptime) String() string {
35+ return fmt.Sprintf("%d days, %d hours, %d minutes and %d seconds", up.Days, up.Hours, up.Minutes, up.Seconds)
36+}
+92,
-0
1@@ -0,0 +1,92 @@
2+package internal
3+
4+import (
5+ "fmt"
6+ "os"
7+ "strings"
8+)
9+
10+func GetPasswd() (Passwd, error) {
11+ var passwd Passwd
12+ text, err := os.ReadFile("/etc/passwd")
13+ if err != nil {
14+ return nil, fmt.Errorf("failed to read /etc/passwd: %v", err)
15+ }
16+ if err := passwd.UnmarshalText(text); err != nil {
17+ return nil, err
18+ }
19+ return passwd, nil
20+}
21+
22+func GetOsRelease() (OsRelease, error) {
23+ var osRelease OsRelease = map[string]string{}
24+ text, err := os.ReadFile("/etc/os-release")
25+ if err != nil {
26+ return nil, fmt.Errorf("failed to read /etc/os-release: %v", err)
27+ }
28+ if err := osRelease.UnmarshalText(text); err != nil {
29+ return nil, err
30+ }
31+ return osRelease, nil
32+}
33+
34+func GetHostname() (string, error) {
35+ var sb strings.Builder
36+ hostname, err := os.ReadFile("/etc/hostname")
37+ if err != nil {
38+ return "", fmt.Errorf("failed to read /etc/hostname: %v", err)
39+ }
40+ _, err = sb.Write(hostname)
41+ if err != nil {
42+ return "", fmt.Errorf("failed to use hostname: %v", err)
43+ }
44+ return strings.TrimSpace(sb.String()), nil
45+}
46+
47+func GetUsername() (string, error) {
48+ userEnvVars := []string{"USER", "username"}
49+ var ok bool
50+ var val string
51+ for _, envvar := range userEnvVars {
52+ val, ok = os.LookupEnv(envvar)
53+ if ok {
54+ break
55+ }
56+ }
57+ if !ok {
58+ return "", fmt.Errorf("failed to get username")
59+ }
60+ return val, nil
61+}
62+
63+func GetShell(user string, passwd Passwd) (string, error) {
64+ pwEnt, err := passwd.Entry(user)
65+ if err != nil {
66+ return "", err
67+ }
68+ return pwEnt.Shell, nil
69+}
70+
71+func GetUptime() (Uptime, error) {
72+ var up Uptime
73+ text, err := os.ReadFile("/proc/uptime")
74+ if err != nil {
75+ return up, fmt.Errorf("failed to read /proc/uptime: %v", err)
76+ }
77+ if err := up.UnmarshalText(text); err != nil {
78+ return up, err
79+ }
80+ return up, nil
81+}
82+
83+func GetTerminal() (string, error) {
84+ val, ok := os.LookupEnv("TERMINAL")
85+ if ok {
86+ return val, nil
87+ }
88+ val, ok = os.LookupEnv("TERM")
89+ if ok {
90+ return val, nil
91+ }
92+ return "", fmt.Errorf("failed to find terminal")
93+}
+32,
-0
1@@ -0,0 +1,32 @@
2+<!DOCTYPE html>
3+<html>
4+ <head>
5+ </head>
6+ <body>
7+ <main>
8+ <p>artur@napoleon</p>
9+ <table>
10+ <tr>
11+ <td>OS</td>
12+ <td>NixOS 26.05 (Yarara)</td>
13+ </tr>
14+ <tr>
15+ <td>Kernel</td>
16+ <td>Linux x86_64 6.18.22</td>
17+ </tr>
18+ <tr>
19+ <td>Uptime</td>
20+ <td>0 days, 13 hours, 50 minutes and 6 seconds</td>
21+ </tr>
22+ <tr>
23+ <td>Shell</td>
24+ <td>/run/current-system/sw/bin/zsh</td>
25+ </tr>
26+ <tr>
27+ <td>Terminal</td>
28+ <td>alacritty</td>
29+ </tr>
30+ </table>
31+ </main>
32+ </body>
33+</html>
+32,
-0
1@@ -0,0 +1,32 @@
2+<!DOCTYPE html>
3+<html>
4+ <head>
5+ </head>
6+ <body>
7+ <main>
8+ <p>{{ .Username}}@{{ .Hostname }}</p>
9+ <table>
10+ <tr>
11+ <td>OS</td>
12+ <td>{{ .OS }}</td>
13+ </tr>
14+ <tr>
15+ <td>Kernel</td>
16+ <td>{{ .KernelName }} {{ .MachineArch }} {{ .KernelRelease }}</td>
17+ </tr>
18+ <tr>
19+ <td>Uptime</td>
20+ <td>{{ .Uptime }}</td>
21+ </tr>
22+ <tr>
23+ <td>Shell</td>
24+ <td>{{ .Shell }}</td>
25+ </tr>
26+ <tr>
27+ <td>Terminal</td>
28+ <td>{{ .Terminal }}</td>
29+ </tr>
30+ </table>
31+ </main>
32+ </body>
33+</html>