master
go 193 lines 7.28 KB
Raw
1 package name
2
3 import (
4 "errors"
5 "fmt"
6 "io"
7 "time"
8
9 cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
10 "github.com/ipfs/kubo/core/commands/cmdutils"
11
12 ipns "github.com/ipfs/boxo/ipns"
13 cmds "github.com/ipfs/go-ipfs-cmds"
14 ke "github.com/ipfs/kubo/core/commands/keyencode"
15 iface "github.com/ipfs/kubo/core/coreiface"
16 options "github.com/ipfs/kubo/core/coreiface/options"
17 )
18
19 var errAllowOffline = errors.New("can't publish while offline: pass `--allow-offline` to override or `--allow-delegated` if Ipns.DelegatedPublishers are set up")
20
21 const (
22 ipfsPathOptionName = "ipfs-path"
23 resolveOptionName = "resolve"
24 allowOfflineOptionName = "allow-offline"
25 allowDelegatedOptionName = "allow-delegated"
26 lifeTimeOptionName = "lifetime"
27 ttlOptionName = "ttl"
28 keyOptionName = "key"
29 quieterOptionName = "quieter"
30 v1compatOptionName = "v1compat"
31 sequenceOptionName = "sequence"
32 )
33
34 var PublishCmd = &cmds.Command{
35 Helptext: cmds.HelpText{
36 Tagline: "Publish IPNS names.",
37 ShortDescription: `
38 IPNS is a PKI namespace, where names are the hashes of public keys, and
39 the private key enables publishing new (signed) values. In both publish
40 and resolve, the default name used is the node's own PeerID,
41 which is the hash of its public key.
42 `,
43 LongDescription: `
44 IPNS is a PKI namespace, where names are the hashes of public keys, and
45 the private key enables publishing new (signed) values. In both publish
46 and resolve, the default name used is the node's own PeerID,
47 which is the hash of its public key.
48
49 You can use the 'ipfs key' commands to list and generate more names and their
50 respective keys.
51
52 Publishing Modes:
53
54 By default, IPNS records are published to both the DHT and any configured
55 HTTP delegated publishers. You can control this behavior with the following flags:
56
57 --allow-offline Allow publishing when offline (publishes to local datastore, network operations are optional)
58 --allow-delegated Allow publishing without DHT connectivity (local + HTTP delegated publishers only)
59
60 Examples:
61
62 Publish an <ipfs-path> with your default name:
63
64 > ipfs name publish /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
65 Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
66
67 Publish without DHT (HTTP delegated publishers only):
68
69 > ipfs name publish --allow-delegated /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
70 Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
71
72 Publish when offline (local publish, network optional):
73
74 > ipfs name publish --allow-offline /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
75 Published to QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n: /ipfs/QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy
76
77 Notes:
78
79 The --ttl option specifies the time duration for caching IPNS records.
80 Lower values like '1m' enable faster updates but increase network load,
81 while the default of 1 hour reduces traffic but may delay propagation.
82 Gateway operators may override this with Ipns.MaxCacheTTL configuration.
83
84 The --sequence option sets a custom sequence number for the IPNS record.
85 The sequence number must be monotonically increasing (greater than the
86 current record's sequence). This is useful for manually coordinating
87 updates across multiple writers. If not specified, the sequence number
88 increments automatically.
89
90 For faster IPNS updates, consider:
91 - Using a lower --ttl value (e.g., '1m' for quick updates)
92 - Enabling PubSub via Ipns.UsePubsub in the config
93
94 `,
95 },
96
97 Arguments: []cmds.Argument{
98 cmds.StringArg(ipfsPathOptionName, true, false, "ipfs path of the object to be published.").EnableStdin(),
99 },
100 Options: []cmds.Option{
101 cmds.StringOption(keyOptionName, "k", "Name of the key to be used or a valid PeerID, as listed by 'ipfs key list -l'.").WithDefault("self"),
102 cmds.BoolOption(resolveOptionName, "Check if the given path can be resolved before publishing.").WithDefault(true),
103 cmds.StringOption(lifeTimeOptionName, "t", `Time duration the signed record will be valid for. Accepts durations such as "300s", "1.5h" or "7d2h45m"`).WithDefault(ipns.DefaultRecordLifetime.String()),
104 cmds.StringOption(ttlOptionName, "Time duration hint, akin to --lifetime, indicating how long to cache this record before checking for updates.").WithDefault(ipns.DefaultRecordTTL.String()),
105 cmds.BoolOption(quieterOptionName, "Q", "Write only final IPNS Name encoded as CIDv1 (for use in /ipns content paths)."),
106 cmds.BoolOption(v1compatOptionName, "Produce a backward-compatible IPNS Record by including fields for both V1 and V2 signatures.").WithDefault(true),
107 cmds.BoolOption(allowOfflineOptionName, "Allow publishing when offline - publishes to local datastore without requiring network connectivity."),
108 cmds.BoolOption(allowDelegatedOptionName, "Allow publishing without DHT connectivity - uses local datastore and HTTP delegated publishers only."),
109 cmds.Uint64Option(sequenceOptionName, "Set a custom sequence number for the IPNS record (must be higher than current)."),
110 ke.OptionIPNSBase,
111 },
112 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
113 api, err := cmdenv.GetApi(env, req)
114 if err != nil {
115 return err
116 }
117
118 allowOffline, _ := req.Options[allowOfflineOptionName].(bool)
119 allowDelegated, _ := req.Options[allowDelegatedOptionName].(bool)
120 compatibleWithV1, _ := req.Options[v1compatOptionName].(bool)
121 kname, _ := req.Options[keyOptionName].(string)
122
123 // Validate flag combinations
124 if allowOffline && allowDelegated {
125 return errors.New("cannot use both --allow-offline and --allow-delegated flags")
126 }
127
128 validTimeOpt, _ := req.Options[lifeTimeOptionName].(string)
129 validTime, err := time.ParseDuration(validTimeOpt)
130 if err != nil {
131 return fmt.Errorf("error parsing lifetime option: %s", err)
132 }
133
134 opts := []options.NamePublishOption{
135 options.Name.AllowOffline(allowOffline),
136 options.Name.AllowDelegated(allowDelegated),
137 options.Name.Key(kname),
138 options.Name.ValidTime(validTime),
139 options.Name.CompatibleWithV1(compatibleWithV1),
140 }
141
142 if ttl, found := req.Options[ttlOptionName].(string); found {
143 d, err := time.ParseDuration(ttl)
144 if err != nil {
145 return err
146 }
147
148 opts = append(opts, options.Name.TTL(d))
149 }
150
151 if sequence, found := req.Options[sequenceOptionName].(uint64); found {
152 opts = append(opts, options.Name.Sequence(sequence))
153 }
154
155 p, err := cmdutils.PathOrCidPath(req.Arguments[0])
156 if err != nil {
157 return err
158 }
159
160 if verifyExists, _ := req.Options[resolveOptionName].(bool); verifyExists {
161 _, err := api.ResolveNode(req.Context, p)
162 if err != nil {
163 return err
164 }
165 }
166
167 name, err := api.Name().Publish(req.Context, p, opts...)
168 if err != nil {
169 if err == iface.ErrOffline {
170 err = errAllowOffline
171 }
172 return err
173 }
174
175 return cmds.EmitOnce(res, &IpnsEntry{
176 Name: name.String(),
177 Value: p.String(),
178 })
179 },
180 Encoders: cmds.EncoderMap{
181 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, ie *IpnsEntry) error {
182 var err error
183 quieter, _ := req.Options[quieterOptionName].(bool)
184 if quieter {
185 _, err = fmt.Fprintln(w, cmdenv.EscNonPrint(ie.Name))
186 } else {
187 _, err = fmt.Fprintf(w, "Published to %s: %s\n", cmdenv.EscNonPrint(ie.Name), cmdenv.EscNonPrint(ie.Value))
188 }
189 return err
190 }),
191 },
192 Type: IpnsEntry{},
193 }