11package main
22
33import (
4+ "bytes"
45 "context"
5- "encoding/json"
6+ "encoding/base64"
7+ "encoding/gob"
8+ "fmt"
69 "log"
10+ "net"
11+ "strings"
712
813 "github.com/hetznercloud/hcloud-go/hcloud"
14+ "github.com/klauspost/compress/zstd"
915)
1016
1117type Database struct {
12- Store map [string ]string
13- Name string
14- Client * hcloud.Client
15- Context context.Context
16- Self * hcloud.SSHKey
17- NoInfo bool
18+ Store map [string ]string
19+ Name string
20+ Client * hcloud.Client
21+ Context context.Context
22+ Self * hcloud.Firewall
23+ NoInfo bool
24+ LastEncodedSize int
1825}
1926
2027func (d * Database ) Init () {
21- keyPlaceholder := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAUqWKFtn3P3p0tOWXMgkfc7aTc5Z17+LSlf50X/ep/Z"
28+ _ , response , err := d . Client . Firewall . Create ( d . Context , hcloud. FirewallCreateOpts { Name : d . Name })
2229
23- database , response , err := d .Client .SSHKey .Create (d .Context , hcloud.SSHKeyCreateOpts {Name : d .Name , PublicKey : keyPlaceholder })
24- if err != nil && response .StatusCode == 409 {
25- log .Fatalf ("database %s already exists" , d .Name )
26- } else if err != nil {
27- log .Fatalf ("unhandled error: %s\n " , err )
30+ if err != nil {
31+ if response != nil && response .StatusCode == 409 {
32+ log .Printf ("database %s already exists" , d .Name )
33+ } else {
34+ log .Fatalf ("unhandled error: %s\n " , err )
35+ }
36+ } else {
37+ log .Printf ("created new database: %s" , d .Name )
2838 }
2939
30- d .Store = database . Labels
40+ d .Fetch ()
3141}
3242
33- func (d * Database ) Fetch () * hcloud.SSHKey {
34- db , _ , err := d .Client .SSHKey .Get (d .Context , d .Name )
35- d .Self = db
36-
43+ func (d * Database ) Fetch () * hcloud.Firewall {
44+ db , _ , err := d .Client .Firewall .Get (d .Context , d .Name )
3745 if err != nil {
3846 log .Fatalf ("error retrieving database: %s\n " , err )
3947 }
48+ d .Self = db
4049
4150 if d .Self != nil {
42- d .Store = d .Self .Labels
43- } else {
44- log .Fatalf ("database %s not found" , d .Name )
51+ store , size , err := rulesToMap (d .Self .Rules )
52+ if err != nil {
53+ log .Printf ("could not parse rules (db might be empty or old format): %s" , err )
54+ if d .Store == nil {
55+ d .Store = make (map [string ]string )
56+ }
57+ } else {
58+ d .Store = store
59+ d .LastEncodedSize = size
60+ }
4561 }
4662
47- checkSize (d )
48-
63+ checkSize (d , d .LastEncodedSize )
4964 return d .Self
5065}
5166
5267func (d * Database ) Set (key , value string ) bool {
5368 d .Fetch ()
69+
70+ if d .Store == nil {
71+ d .Store = make (map [string ]string )
72+ }
73+
5474 d .Store [key ] = value
5575
56- database , _ , err := d .Client .SSHKey .Update (d .Context , d .Self , hcloud.SSHKeyUpdateOpts {Labels : d .Store })
76+ firewallRules , encodedLength , err := mapToRules (d .Store )
77+
78+ _ , _ , err = d .Client .Firewall .SetRules (d .Context , d .Self , hcloud.FirewallSetRulesOpts {Rules : firewallRules })
5779
5880 if err != nil {
5981 log .Fatalf ("error updating db: %s\n " , err )
6082 }
6183
62- d .Store = database .Labels
63-
84+ d .LastEncodedSize = encodedLength
6485 log .Println ("OK" )
65- checkSize (d )
86+ checkSize (d , encodedLength )
6687
6788 return true
6889}
@@ -75,21 +96,146 @@ func (d *Database) Get(key string) string {
7596func (d * Database ) List () []string {
7697 d .Fetch ()
7798
78- keys := make ([]string , len (d .Store ))
99+ keys := make ([]string , 0 , len (d .Store ))
79100
80- i := 0
81101 for k := range d .Store {
82- keys [i ] = k
83- i ++
102+ keys = append (keys , k )
84103 }
85104
86105 return keys
87106}
88107
89- func checkSize (db * Database ) {
90- if ! db .NoInfo {
91- jsonStr , _ := json .Marshal (db .Store )
92- log .Printf ("[Info] Database usage: %.2f%%" , float64 (len (jsonStr ))/ float64 (maxDBBytes )* 100 )
108+ func (d * Database ) Delete (key string ) bool {
109+ d .Fetch ()
110+
111+ if _ , exists := d .Store [key ]; ! exists {
112+ log .Printf ("Key '%s' not found, nothing to delete." , key )
113+ return false
93114 }
115+
116+ delete (d .Store , key )
117+
118+ firewallRules , encodedLength , err := mapToRules (d .Store )
119+ if err != nil {
120+ log .Fatalf ("Error encoding data after delete: %s" , err )
121+ }
122+
123+ _ , _ , err = d .Client .Firewall .SetRules (d .Context , d .Self , hcloud.FirewallSetRulesOpts {
124+ Rules : firewallRules ,
125+ })
126+
127+ if err != nil {
128+ log .Fatalf ("error updating db during delete: %s\n " , err )
129+ }
130+
131+ d .LastEncodedSize = encodedLength
132+ log .Println ("Deleted key:" , key )
133+ checkSize (d , encodedLength )
134+
135+ return true
94136}
95137
138+ func (d * Database ) Clear () {
139+ d .Fetch ()
140+
141+ if d .Self == nil {
142+ log .Fatal ("Could not clear database: Firewall not found or not initialized." )
143+ }
144+
145+ d .Store = make (map [string ]string )
146+
147+ _ , _ , err := d .Client .Firewall .SetRules (d .Context , d .Self , hcloud.FirewallSetRulesOpts {
148+ Rules : []hcloud.FirewallRule {},
149+ })
150+
151+ if err != nil {
152+ log .Fatalf ("Failed to clear database: %s" , err )
153+ }
154+
155+ d .LastEncodedSize = 0
156+ log .Println ("Database cleared successfully." )
157+ }
158+
159+ func checkSize (db * Database , currentLength int ) {
160+ if db .NoInfo {
161+ return
162+ }
163+
164+ maxChars := 500 * 255
165+ usage := (float64 (currentLength ) / float64 (maxChars )) * 100
166+
167+ log .Printf ("[Info] Storage: %d/%d chars (%.2f%% used)" , currentLength , maxChars , usage )
168+ }
169+
170+ func rulesToMap (rules []hcloud.FirewallRule ) (map [string ]string , int , error ) {
171+ store := map [string ]string {}
172+ var sb strings.Builder
173+ sb .Grow (len (rules ) * 255 )
174+
175+ for _ , rule := range rules {
176+ if rule .Description != nil {
177+ sb .WriteString (* rule .Description )
178+ }
179+ }
180+ tempString := sb .String ()
181+ totalLength := len (tempString )
182+ if totalLength == 0 {
183+ return store , 0 , nil
184+ }
185+
186+ decodedBytes , err := base64 .StdEncoding .DecodeString (tempString )
187+ if err != nil {
188+ return store , totalLength , err
189+ }
190+
191+ zr , err := zstd .NewReader (bytes .NewReader (decodedBytes ))
192+ if err != nil {
193+ return store , totalLength , err
194+ }
195+ defer zr .Close ()
196+
197+ err = gob .NewDecoder (zr ).Decode (& store )
198+ return store , totalLength , err
199+ }
200+
201+ func mapToRules (store map [string ]string ) ([]hcloud.FirewallRule , int , error ) {
202+ var gobBuf bytes.Buffer
203+ if err := gob .NewEncoder (& gobBuf ).Encode (store ); err != nil {
204+ return nil , 0 , err
205+ }
206+
207+ var compressedBuf bytes.Buffer
208+ zw , _ := zstd .NewWriter (& compressedBuf )
209+ zw .Write (gobBuf .Bytes ())
210+ zw .Close ()
211+
212+ encodedString := base64 .StdEncoding .EncodeToString (compressedBuf .Bytes ())
213+ totalLength := len (encodedString )
214+
215+ var rules []hcloud.FirewallRule
216+ limit := 255
217+ _ , dummyNet , _ := net .ParseCIDR ("0.0.0.0/32" )
218+
219+ for i := 0 ; i < len (encodedString ); i += limit {
220+ end := i + limit
221+ if end > len (encodedString ) {
222+ end = len (encodedString )
223+ }
224+
225+ chunk := encodedString [i :end ] // Direct slice is safer for ASCII Base64
226+
227+ rules = append (rules , hcloud.FirewallRule {
228+ Description : hcloud .Ptr (chunk ),
229+ Direction : hcloud .FirewallRuleDirectionIn ,
230+ Protocol : hcloud .FirewallRuleProtocolTCP ,
231+ Port : hcloud .Ptr ("80" ),
232+ SourceIPs : []net.IPNet {* dummyNet },
233+ })
234+ }
235+
236+ for i , r := range rules {
237+ fmt .Printf ("Rule %d: Desc Length: %d, Protocol: %s\n " , i , len (* r .Description ), r .Protocol )
238+ }
239+
240+ return rules , totalLength , nil
241+ }
0 commit comments